Configurator/UI/Controls/SelectablePictureBox.cs (140 lines of code) (raw):
/* Copyright (c) 2023, 2024, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0, as
published by the Free Software Foundation.
This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms, as
designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
using MySql.Configurator.Base.Classes;
namespace MySql.Configurator.UI.Controls
{
/// <summary>
/// Represents a <see cref="PictureBox"/> that can have a selectable status reflected visually.
/// </summary>
public class SelectablePictureBox : PictureBox
{
#region Fields
/// <summary>
/// Preserves a cached image to represent a selected state.
/// </summary>
private Image _cachedSelectedImage;
/// <summary>
/// Preserves a cached image to represent an unselected state.
/// </summary>
private Image _cachedUnselectedImage;
/// <summary>
/// Flag indicating whether the <see cref="PictureBox.Image"/> is automatically set to a version of the image in <seealso cref="PictureBox.ImageLocation"/> corresponding to the selected state.
/// </summary>
private bool _changeImageOnSelection;
/// <summary>
/// Flag indicating whether the picture is selected or not.
/// </summary>
private bool _selected;
#endregion Fields
#region Events
/// <summary>
/// Occurs when the value of the <see cref="Selected"/> property changes.
/// </summary>
public event EventHandler SelectedStateChanged;
#endregion Events
/// <summary>
/// Initializes a new instance of the <see cref="SelectablePictureBox"/> class.
/// </summary>
public SelectablePictureBox()
{
_cachedSelectedImage = null;
_cachedUnselectedImage = null;
_changeImageOnSelection = true;
_selected = false;
ChangeImage(_selected);
}
#region Properties
/// <summary>
/// Gets or sets a value indicating whether the <see cref="PictureBox.Image"/> is automatically set to a version of the image in <seealso cref="PictureBox.ImageLocation"/> corresponding to the selected state.
/// </summary>
[Category("MySQL Custom"), Description("A value indicating whether the picture is selected or not."),
DefaultValue(true)]
public bool ChangeImageOnSelection
{
get => _changeImageOnSelection;
set
{
var oldChangeImageValue = _selected;
_changeImageOnSelection = value;
if (oldChangeImageValue != _changeImageOnSelection)
{
ChangeImage(_selected);
}
}
}
/// <summary>
/// Gets or sets the path or URL for the image to display in the <see cref="SelectablePictureBox"/>.
/// </summary>
public new Image InitialImage
{
get => base.InitialImage;
set
{
base.InitialImage = value;
UpdateCachedImages();
ChangeImage(_selected);
}
}
/// <summary>
/// Gets or sets the path or URL for the image to display in the <see cref="SelectablePictureBox"/>.
/// </summary>
public new string ImageLocation
{
get => base.ImageLocation;
set
{
base.ImageLocation = value;
UpdateCachedImages();
ChangeImage(_selected);
}
}
/// <summary>
/// Gets or sets a value indicating whether the picture is selected or not.
/// </summary>
/// <remarks>
/// If <see cref="ChangeImageOnSelection"/> is <c>true</c>, the <seealso cref="PictureBox.Image"/> property will be automatically set to a version of the image in <seealso cref="PictureBox.ImageLocation"/> corresponding to the selected state.
/// </remarks>
[Category("MySQL Custom"), Description("A value indicating whether the picture is selected or not."), DefaultValue(false)]
public bool Selected
{
get => _selected;
set
{
var oldSelectedValue = _selected;
_selected = value;
if (oldSelectedValue != _selected)
{
ChangeImage(_selected);
OnSelectedStateChanged();
}
}
}
#endregion Properties
/// <summary>
/// Changes the <see cref="Image"/> value to one of the cached images.
/// </summary>
public void ChangeImage(bool selected)
{
Image = ChangeImageOnSelection
&& selected
? _cachedSelectedImage
: _cachedUnselectedImage;
}
/// <summary>
/// Updates the cached images.
/// </summary>
public void UpdateCachedImages()
{
if (!string.IsNullOrEmpty(ImageLocation)
&& File.Exists(ImageLocation))
{
_cachedSelectedImage = Image.FromFile(ImageLocation);
_cachedUnselectedImage = Darken(new Bitmap(ImageLocation));
}
else if (InitialImage != null)
{
_cachedSelectedImage = InitialImage;
_cachedUnselectedImage = Darken(InitialImage as Bitmap);
}
}
/// <summary>
/// Raises the <see cref="SelectedStateChanged"/> event.
/// </summary>
protected void OnSelectedStateChanged()
{
SelectedStateChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Gets a <see cref="ColorMatrix"/> that can change a <see cref="Bitmap"/> brightness, contrast and color saturation.
/// </summary>
/// <param name="brightnessFactor">The amount of "sunlight" in the picture. Ranges between -1.0f to 1.0f, -1.0f = pitch-black, 0 = original, 1.0f = total white.</param>
/// <param name="contrastFactor">The amount of difference between red, green, and blue colors. Must be greater than 0. 0 = complete gray, 1 = original, > 1.0f = glaring white.</param>
/// <param name="saturationFactor">The amount of "grayscale-ness" in the picture. Must be greater than 0. 0 = grayscale, 1.0f = original colors, > 1.0f = very colorful.</param>
/// <returns>A <see cref="ColorMatrix"/> that can change a <see cref="Bitmap"/> brightness, contrast and color saturation..</returns>
private ColorMatrix CreateColorMatrix(float brightnessFactor = 0, float contrastFactor = 1.0f, float saturationFactor = 1.0f)
{
const float LUM_RED = 0.3086f; // or 0.2125f
const float LUM_GREEN = 0.6094f; // or 0.7154f
const float LUM_BLUE = 0.0820f; // or 0.0721f
brightnessFactor = Math.Min(1.0f, brightnessFactor);
brightnessFactor = Math.Max(-1.0f, brightnessFactor);
contrastFactor = Math.Max(0, contrastFactor);
saturationFactor = Math.Max(0, saturationFactor);
var whiteFactor = (1.0f - contrastFactor) / 2.0f + brightnessFactor;
var redFactor = (1.0f - saturationFactor) * LUM_RED * contrastFactor;
var greenFactor = (1.0f - saturationFactor) * LUM_GREEN * contrastFactor;
var blueFactor = (1.0f - saturationFactor) * LUM_BLUE * contrastFactor;
var redFactor2 = redFactor + saturationFactor * contrastFactor;
var greenFactor2 = greenFactor + saturationFactor * contrastFactor;
var blueFactor2 = blueFactor + saturationFactor * contrastFactor;
return new ColorMatrix(new[]
{
new[] { redFactor2, redFactor, redFactor, 0, 0 },
new[] { greenFactor, greenFactor2, greenFactor, 0, 0 },
new[] { blueFactor, blueFactor, blueFactor2, 0, 0 },
new[] { 0, 0, 0, 1.0f, 0 },
new[] { whiteFactor, whiteFactor, whiteFactor, 0, 1 }
});
}
/// <summary>
/// Creates a new bitmap based on a given bitmap with its colors darkened.
/// </summary>
/// <param name="original">The bitmap to convert.</param>
/// <param name="factor">A value between 0 and 1.00f, (0 means no change, default is 0.5f).</param>
/// <returns>A new bitmap based on a given bitmap with its colors darkened.</returns>
private Bitmap Darken(Bitmap original, float factor = 0.7f)
{
return TransformColors(original, 0, 1.0f - factor, 1.0f - factor);
}
/// <summary>
/// Creates a new bitmap based on a given bitmap with its colors transformed by the given factors.
/// </summary>
/// <param name="original">The bitmap to change.</param>
/// <param name="brightnessFactor">The amount of "sunlight" in the picture. Ranges between -1.0f to 1.0f, -1.0f = pitch-black, 0 = original, 1.0f = total white.</param>
/// <param name="contrastFactor">The amount of difference between red, green, and blue colors. Must be greater than 0. 0 = complete gray, 1 = original, > 1.0f = glaring white.</param>
/// <param name="saturationFactor">The amount of "grayscale-ness" in the picture. Must be greater than 0. 0 = grayscale, 1.0f = original colors, > 1.0f = very colorful.</param>
/// <returns>A new bitmap based on a given bitmap with its colors transformed by the given factors.</returns>
private Bitmap TransformColors(Bitmap original, float brightnessFactor = 0, float contrastFactor = 1.0f, float saturationFactor = 1.0f)
{
return original.ChangeColors(CreateColorMatrix(brightnessFactor, contrastFactor, saturationFactor));
}
}
}