#region Imports
using DPumpHydr.WinFrmUI.RLT.Extension;
using DPumpHydr.WinFrmUI.RLT.Helper;
using DPumpHydr.WinFrmUI.RLT.Manager;
using DPumpHydr.WinFrmUI.RLT.Util;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Globalization;
using System.Windows.Forms;
using static DPumpHydr.WinFrmUI.RLT.Helper.MaterialDrawHelper;
using static DPumpHydr.WinFrmUI.RLT.Util.MaterialAnimations;
#endregion
namespace DPumpHydr.WinFrmUI.RLT.Controls
{
#region MaterialButton
public class MaterialButton : System.Windows.Forms.Button, MaterialControlI
{
private const int ICON_SIZE = 24;
private const int MINIMUMWIDTH = 64;
private const int MINIMUMWIDTHICONONLY = 36; //64;
private const int HEIGHTDEFAULT = 36;
private const int HEIGHTDENSE = 32;
// icons
private TextureBrush iconsBrushes;
///
/// Gets or sets the Depth
///
[Browsable(false)]
public int Depth { get; set; }
///
/// Gets the SkinManager
///
[Browsable(false)]
public MaterialSkinManager SkinManager => MaterialSkinManager.Instance;
///
/// Gets or sets the MouseState
///
[Browsable(false)]
public MaterialMouseState MouseState { get; set; }
public enum MaterialButtonType
{
Text,
Outlined,
Contained
}
public enum MaterialButtonDensity
{
Default,
Dense
}
public enum MaterialIconType
{
Rebase,
Default
}
[Category("Material")]
public MaterialIconType IconType { get; set; }
[Browsable(false)]
public Color NoAccentTextColor { get; set; }
[Category("Material")]
public bool UseAccentColor
{
get => useAccentColor;
set { useAccentColor = value; Invalidate(); }
}
[Category("Material")]
///
/// Gets or sets a value indicating whether HighEmphasis
///
public bool HighEmphasis
{
get => highEmphasis;
set { highEmphasis = value; Invalidate(); }
}
[DefaultValue(true)]
[Category("Material")]
[Description("Draw Shadows around control")]
public bool DrawShadows
{
get => drawShadows;
set { drawShadows = value; Invalidate(); }
}
[Category("Material")]
public MaterialButtonType Type
{
get => type;
set { type = value; preProcessIcons(); Invalidate(); }
}
[Category("Material")]
///
/// Gets or sets a value indicating button density
///
public MaterialButtonDensity Density
{
get => _density;
set
{
_density = value;
if (_density == MaterialButtonDensity.Dense)
{
Size = new Size(Size.Width, HEIGHTDENSE);
}
else
{
Size = new Size(Size.Width, HEIGHTDEFAULT);
}
Invalidate();
}
}
public enum CharacterCasingEnum
{
Normal,
Lower,
Upper,
Title
}
public CharacterCasingEnum _cc;
[Category("Behavior"), DefaultValue(CharacterCasingEnum.Upper), Description("Change capitalization of Text property")]
public CharacterCasingEnum CharacterCasing
{
get => _cc;
set
{
_cc = value;
Invalidate();
}
}
protected override void InitLayout()
{
base.InitLayout();
Invalidate();
LocationChanged += (sender, e) => { if (DrawShadows) { Parent?.Invalidate(); } };
}
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
if (drawShadows && Parent != null)
{
AddShadowPaintEvent(Parent, drawShadowOnParent);
}
if (_oldParent != null)
{
RemoveShadowPaintEvent(_oldParent, drawShadowOnParent);
}
_oldParent = Parent;
}
private Control _oldParent;
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if (Parent == null)
{
return;
}
if (Visible)
{
AddShadowPaintEvent(Parent, drawShadowOnParent);
}
else
{
RemoveShadowPaintEvent(Parent, drawShadowOnParent);
}
}
protected override void OnEnabledChanged(EventArgs e)
{
base.OnEnabledChanged(e);
Invalidate();
}
private bool _shadowDrawEventSubscribed = false;
private void AddShadowPaintEvent(Control control, PaintEventHandler shadowPaintEvent)
{
if (_shadowDrawEventSubscribed)
{
return;
}
control.Paint += shadowPaintEvent;
control.Invalidate();
_shadowDrawEventSubscribed = true;
}
private void RemoveShadowPaintEvent(Control control, PaintEventHandler shadowPaintEvent)
{
if (!_shadowDrawEventSubscribed)
{
return;
}
control.Paint -= shadowPaintEvent;
control.Invalidate();
_shadowDrawEventSubscribed = false;
}
private readonly AnimationManager _hoverAnimationManager = null;
private readonly AnimationManager _focusAnimationManager = null;
private readonly AnimationManager _animationManager = null;
///
/// Defines the _textSize
///
private SizeF _textSize;
///
/// Defines the _icon
///
private Image _icon;
private bool drawShadows;
private bool highEmphasis;
private bool useAccentColor;
private MaterialButtonType type;
private MaterialButtonDensity _density;
[Category("Material")]
///
/// Gets or sets the Icon
///
public Image Icon
{
get => _icon;
set
{
_icon = value;
preProcessIcons();
if (AutoSize)
{
Refresh();
}
Invalidate();
}
}
[DefaultValue(true)]
public override bool AutoSize
{
get => base.AutoSize;
set => base.AutoSize = value;
}
///
/// Initializes a new instance of the class.
///
public MaterialButton()
{
DrawShadows = true;
HighEmphasis = true;
UseAccentColor = false;
Type = MaterialButtonType.Contained;
Density = MaterialButtonDensity.Default;
NoAccentTextColor = Color.Empty;
CharacterCasing = CharacterCasingEnum.Upper;
_animationManager = new AnimationManager(false)
{
Increment = 0.03,
AnimationType = AnimationType.EaseOut
};
_hoverAnimationManager = new AnimationManager
{
Increment = 0.12,
AnimationType = AnimationType.Linear
};
_focusAnimationManager = new AnimationManager
{
Increment = 0.12,
AnimationType = AnimationType.Linear
};
SkinManager.ColorSchemeChanged += sender =>
{
preProcessIcons();
};
SkinManager.ThemeChanged += sender =>
{
preProcessIcons();
};
_hoverAnimationManager.OnAnimationProgress += sender => Invalidate();
_focusAnimationManager.OnAnimationProgress += sender => Invalidate();
_animationManager.OnAnimationProgress += sender => Invalidate();
AutoSizeMode = AutoSizeMode.GrowAndShrink;
AutoSize = true;
Margin = new Padding(4, 6, 4, 6);
Padding = new Padding(0);
}
///
/// Gets or sets the Text
///
public override string Text
{
get => base.Text;
set
{
base.Text = value;
if (!string.IsNullOrEmpty(value))
{
_textSize = CreateGraphics().MeasureString(value.ToUpper(), SkinManager.GetFontByType(MaterialSkinManager.FontType.Button));
}
else
{
_textSize.Width = 0;
_textSize.Height = 0;
}
if (AutoSize)
{
Refresh();
}
Invalidate();
}
}
private void drawShadowOnParent(object sender, PaintEventArgs e)
{
if (Parent == null)
{
RemoveShadowPaintEvent((Control)sender, drawShadowOnParent);
return;
}
if (!DrawShadows || Type != MaterialButtonType.Contained || Parent == null)
{
return;
}
// paint shadow on parent
Graphics gp = e.Graphics;
Rectangle rect = new(Location, ClientRectangle.Size);
gp.SmoothingMode = SmoothingMode.AntiAlias;
MaterialDrawHelper.DrawSquareShadow(gp, rect);
}
private void preProcessIcons()
{
if (Icon == null)
{
return;
}
int newWidth, newHeight;
//Resize icon if greater than ICON_SIZE
if (Icon.Width > ICON_SIZE || Icon.Height > ICON_SIZE)
{
//calculate aspect ratio
float aspect = Icon.Width / (float)Icon.Height;
//calculate new dimensions based on aspect ratio
newWidth = (int)(ICON_SIZE * aspect);
newHeight = (int)(newWidth / aspect);
//if one of the two dimensions exceed the box dimensions
if (newWidth > ICON_SIZE || newHeight > ICON_SIZE)
{
//depending on which of the two exceeds the box dimensions set it as the box dimension and calculate the other one based on the aspect ratio
if (newWidth > newHeight)
{
newWidth = ICON_SIZE;
newHeight = (int)(newWidth / aspect);
}
else
{
newHeight = ICON_SIZE;
newWidth = (int)(newHeight * aspect);
}
}
}
else
{
newWidth = Icon.Width;
newHeight = Icon.Height;
}
Bitmap IconResized = new(Icon, newWidth, newHeight);
// Calculate lightness and color
float l = (SkinManager.Theme == MaterialSkinManager.Themes.LIGHT & (highEmphasis == false | Enabled == false | Type != MaterialButtonType.Contained)) ? 0f : 1.5f;
// Create matrices
float[][] matrixGray = {
new float[] { 0, 0, 0, 0, 0}, // Red scale factor
new float[] { 0, 0, 0, 0, 0}, // Green scale factor
new float[] { 0, 0, 0, 0, 0}, // Blue scale factor
new float[] { 0, 0, 0, Enabled ? .7f : .3f, 0}, // alpha scale factor
new float[] { l, l, l, 0, 1}};// offset
ColorMatrix colorMatrixGray = new(matrixGray);
ImageAttributes grayImageAttributes = new();
// Set color matrices
grayImageAttributes.SetColorMatrix(colorMatrixGray, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
// Image Rect
Rectangle destRect = new(0, 0, ICON_SIZE, ICON_SIZE);
// Create a pre-processed copy of the image (GRAY)
Bitmap bgray = new(destRect.Width, destRect.Height);
using (Graphics gGray = Graphics.FromImage(bgray))
{
gGray.DrawImage(IconResized,
new Point[] {
new(0, 0),
new(destRect.Width, 0),
new(0, destRect.Height),
},
destRect, GraphicsUnit.Pixel, grayImageAttributes);
}
// added processed image to brush for drawing
TextureBrush textureBrushGray = new(bgray);
textureBrushGray.WrapMode = WrapMode.Clamp;
// Translate the brushes to the correct positions
Rectangle iconRect = new(8, (Height / 2) - (ICON_SIZE / 2), ICON_SIZE, ICON_SIZE);
textureBrushGray.TranslateTransform(iconRect.X + (iconRect.Width / 2) - (IconResized.Width / 2),
iconRect.Y + (iconRect.Height / 2) - (IconResized.Height / 2));
iconsBrushes = textureBrushGray;
}
///
/// The OnPaint
///
/// The pevent
protected override void OnPaint(PaintEventArgs pevent)
{
Graphics g = pevent.Graphics;
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
g.SmoothingMode = SmoothingMode.AntiAlias;
double hoverAnimProgress = _hoverAnimationManager.GetProgress();
double focusAnimProgress = _focusAnimationManager.GetProgress();
g.Clear(Parent.BackColor == Color.Transparent ? ((Parent.Parent == null || (Parent.Parent != null && Parent.Parent.BackColor == Color.Transparent)) ? SkinManager.BackgroundColor : Parent.Parent.BackColor) : Parent.BackColor);
// button rectand path
RectangleF buttonRectF = new(ClientRectangle.Location, ClientRectangle.Size);
buttonRectF.X -= 0.5f;
buttonRectF.Y -= 0.5f;
GraphicsPath buttonPath = MaterialDrawHelper.CreateRoundRect(buttonRectF, 4);
// button shadow (blend with form shadow)
MaterialDrawHelper.DrawSquareShadow(g, ClientRectangle);
if (Type == MaterialButtonType.Contained)
{
// draw button rect
// Disabled
if (!Enabled)
{
using SolidBrush disabledBrush = new(MaterialDrawHelper.BlendColor(Parent.BackColor, SkinManager.BackgroundDisabledColor, SkinManager.BackgroundDisabledColor.A));
g.FillPath(disabledBrush, buttonPath);
}
// High emphasis
else if (HighEmphasis)
{
g.FillPath(UseAccentColor ? SkinManager.ColorScheme.AccentBrush : SkinManager.ColorScheme.PrimaryBrush, buttonPath);
}
// Mormal
else
{
using SolidBrush normalBrush = new(SkinManager.BackgroundColor);
g.FillPath(normalBrush, buttonPath);
}
}
else
{
g.Clear(Parent.BackColor);
}
//Hover
if (hoverAnimProgress > 0)
{
using SolidBrush hoverBrush = new(Color.FromArgb(
(int)(HighEmphasis && Type == MaterialButtonType.Contained ? hoverAnimProgress * 80 : hoverAnimProgress * SkinManager.BackgroundHoverColor.A), (UseAccentColor ? (HighEmphasis && Type == MaterialButtonType.Contained ?
SkinManager.ColorScheme.AccentColor.Lighten(0.5f) : // Contained with Emphasis - with accent
SkinManager.ColorScheme.AccentColor) : // Not Contained Or Low Emphasis - with accent
(Type == MaterialButtonType.Contained && HighEmphasis ? SkinManager.ColorScheme.LightPrimaryColor : // Contained with Emphasis without accent
SkinManager.ColorScheme.PrimaryColor)).RemoveAlpha())); // Normal or Emphasis without accent
g.FillPath(hoverBrush, buttonPath);
}
//Focus
if (focusAnimProgress > 0)
{
using SolidBrush focusBrush = new(Color.FromArgb(
(int)(HighEmphasis && Type == MaterialButtonType.Contained ? focusAnimProgress * 80 : focusAnimProgress * SkinManager.BackgroundFocusColor.A), (UseAccentColor ? (HighEmphasis && Type == MaterialButtonType.Contained ?
SkinManager.ColorScheme.AccentColor.Lighten(0.5f) : // Contained with Emphasis - with accent
SkinManager.ColorScheme.AccentColor) : // Not Contained Or Low Emphasis - with accent
(Type == MaterialButtonType.Contained && HighEmphasis ? SkinManager.ColorScheme.LightPrimaryColor : // Contained with Emphasis without accent
SkinManager.ColorScheme.PrimaryColor)).RemoveAlpha())); // Normal or Emphasis without accent
g.FillPath(focusBrush, buttonPath);
}
if (Type == MaterialButtonType.Outlined)
{
using Pen outlinePen = new(Enabled ? SkinManager.DividersAlternativeColor : SkinManager.DividersColor, 1);
buttonRectF.X += 0.5f;
buttonRectF.Y += 0.5f;
g.DrawPath(outlinePen, buttonPath);
}
//Ripple
if (_animationManager.IsAnimating())
{
g.Clip = new Region(buttonRectF);
for (int i = 0; i < _animationManager.GetAnimationCount(); i++)
{
double animationValue = _animationManager.GetProgress(i);
Point animationSource = _animationManager.GetSource(i);
using Brush rippleBrush = new SolidBrush(
Color.FromArgb((int)(100 - (animationValue * 100)), // Alpha animation
Type == MaterialButtonType.Contained && HighEmphasis ? (UseAccentColor ?
SkinManager.ColorScheme.AccentColor.Lighten(0.5f) : // Emphasis with accent
SkinManager.ColorScheme.LightPrimaryColor) : // Emphasis
(UseAccentColor ? SkinManager.ColorScheme.AccentColor : // Normal with accent
SkinManager.Theme == MaterialSkinManager.Themes.LIGHT ? SkinManager.ColorScheme.PrimaryColor : SkinManager.ColorScheme.LightPrimaryColor))); // Normal
int rippleSize = (int)(animationValue * Width * 2);
g.FillEllipse(rippleBrush, new Rectangle(animationSource.X - (rippleSize / 2), animationSource.Y - (rippleSize / 2), rippleSize, rippleSize));
}
g.ResetClip();
}
//Text
Rectangle textRect = ClientRectangle;
if (Icon != null)
{
textRect.Width -= 8 + ICON_SIZE + 4 + 8; // left padding + icon width + space between Icon and Text + right padding
textRect.X += 8 + ICON_SIZE + 4; // left padding + icon width + space between Icon and Text
}
Color textColor = Enabled ? (HighEmphasis ? (Type is MaterialButtonType.Text or MaterialButtonType.Outlined) ?
UseAccentColor ? SkinManager.ColorScheme.AccentColor : // Outline or Text and accent and emphasis
NoAccentTextColor == Color.Empty ?
SkinManager.ColorScheme.PrimaryColor : // Outline or Text and emphasis
NoAccentTextColor : // User defined Outline or Text and emphasis
SkinManager.ColorScheme.TextColor : // Contained and Emphasis
SkinManager.TextHighEmphasisColor) : // Cointained and accent
SkinManager.TextDisabledOrHintColor; // Disabled
using (MaterialNativeTextRenderer NativeText = new(g))
{
NativeText.DrawMultilineTransparentText(
CharacterCasing == CharacterCasingEnum.Upper ? base.Text.ToUpper() : CharacterCasing == CharacterCasingEnum.Lower ? base.Text.ToLower() :
CharacterCasing == CharacterCasingEnum.Title ? CultureInfo.CurrentCulture.TextInfo.ToTitleCase(base.Text.ToLower()) : base.Text,
SkinManager.GetLogFontByType(MaterialSkinManager.FontType.Button),
textColor,
textRect.Location,
textRect.Size,
MaterialNativeTextRenderer.TextAlignFlags.Center | MaterialNativeTextRenderer.TextAlignFlags.Middle);
}
//Icon
Rectangle iconRect = new(8, (Height / 2) - (ICON_SIZE / 2), ICON_SIZE, ICON_SIZE);
if (string.IsNullOrEmpty(Text))
{
// Center Icon
iconRect.X += 2;
}
if (Icon != null)
{
if (IconType == MaterialIconType.Rebase)
{
g.FillRectangle(iconsBrushes, iconRect);
}
else
{
g.DrawImage(Icon, iconRect);
}
}
}
///
/// The GetPreferredSize
///
/// The
private Size GetPreferredSize()
{
return GetPreferredSize(Size);
}
///
/// The GetPreferredSize
///
/// The proposedSize
/// The
public override Size GetPreferredSize(Size proposedSize)
{
Size s = base.GetPreferredSize(proposedSize);
// Provides extra space for proper padding for content
int extra = 16;
if (Icon != null)
{
// 24 is for icon size
// 4 is for the space between icon & text
extra += ICON_SIZE + 4;
}
if (AutoSize)
{
s.Width = (int)Math.Ceiling(_textSize.Width);
s.Width += extra;
s.Height = HEIGHTDEFAULT;
}
else
{
s.Width += extra;
s.Height = HEIGHTDEFAULT;
}
if (Icon != null && Text.Length == 0 && s.Width < MINIMUMWIDTHICONONLY)
{
s.Width = MINIMUMWIDTHICONONLY;
}
else if (s.Width < MINIMUMWIDTH)
{
s.Width = MINIMUMWIDTH;
}
return s;
}
///
/// The OnCreateControl
///
protected override void OnCreateControl()
{
base.OnCreateControl();
// before checking DesignMode property, as long as we need see Icon in proper position
Resize += (sender, args) => { preProcessIcons(); Invalidate(); };
if (DesignMode)
{
return;
}
MouseState = MaterialMouseState.OUT;
MouseEnter += (sender, args) =>
{
MouseState = MaterialMouseState.HOVER;
_hoverAnimationManager.StartNewAnimation(AnimationDirection.In);
Invalidate();
};
MouseLeave += (sender, args) =>
{
MouseState = MaterialMouseState.OUT;
_hoverAnimationManager.StartNewAnimation(AnimationDirection.Out);
Invalidate();
};
MouseDown += (sender, args) =>
{
if (args.Button == MouseButtons.Left)
{
MouseState = MaterialMouseState.DOWN;
_animationManager.StartNewAnimation(AnimationDirection.In, args.Location);
Invalidate();
}
};
MouseUp += (sender, args) =>
{
MouseState = MaterialMouseState.HOVER;
Invalidate();
};
GotFocus += (sender, args) =>
{
_focusAnimationManager.StartNewAnimation(AnimationDirection.In);
Invalidate();
};
LostFocus += (sender, args) =>
{
MouseState = MaterialMouseState.OUT;
_focusAnimationManager.StartNewAnimation(AnimationDirection.Out);
Invalidate();
};
PreviewKeyDown += (object sender, PreviewKeyDownEventArgs e) =>
{
if (e.KeyCode is Keys.Enter or Keys.Space)
{
_animationManager.StartNewAnimation(AnimationDirection.In, new Point(ClientRectangle.Width >> 1, ClientRectangle.Height >> 1));
Invalidate();
}
};
}
}
#endregion
}