#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 }