using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Text; using System.Windows.Forms; using MaterialSkin.Animations; namespace MaterialSkin.Controls { public class MaterialTabSelector : Control, IMaterialControl { [Browsable(false)] public int Depth { get; set; } [Browsable(false)] public MaterialSkinManager SkinManager => MaterialSkinManager.Instance; [Browsable(false)] public MouseState MouseState { get; set; } private MaterialTabControl _baseTabControl; public MaterialTabControl BaseTabControl { get { return _baseTabControl; } set { _baseTabControl = value; if (_baseTabControl == null) return; _previousSelectedTabIndex = _baseTabControl.SelectedIndex; _baseTabControl.Deselected += (sender, args) => { _previousSelectedTabIndex = _baseTabControl.SelectedIndex; }; _baseTabControl.SelectedIndexChanged += (sender, args) => { _animationManager.SetProgress(0); _animationManager.StartNewAnimation(AnimationDirection.In); }; _baseTabControl.ControlAdded += delegate { Invalidate(); }; _baseTabControl.ControlRemoved += delegate { Invalidate(); }; } } private int _previousSelectedTabIndex; private Point _animationSource; private readonly AnimationManager _animationManager; private List _tabRects; private const int TAB_HEADER_PADDING = 24; private const int TAB_INDICATOR_HEIGHT = 2; public MaterialTabSelector() { SetStyle(ControlStyles.DoubleBuffer | ControlStyles.OptimizedDoubleBuffer, true); Height = 48; _animationManager = new AnimationManager { AnimationType = AnimationType.EaseOut, Increment = 0.04 }; _animationManager.OnAnimationProgress += sender => Invalidate(); } protected override void OnPaint(PaintEventArgs e) { var g = e.Graphics; g.TextRenderingHint = TextRenderingHint.AntiAlias; g.Clear(SkinManager.ColorScheme.PrimaryColor); if (_baseTabControl == null) return; if (!_animationManager.IsAnimating() || _tabRects == null || _tabRects.Count != _baseTabControl.TabCount) UpdateTabRects(); var animationProgress = _animationManager.GetProgress(); //Click feedback if (_animationManager.IsAnimating()) { var rippleBrush = new SolidBrush(Color.FromArgb((int)(51 - (animationProgress * 50)), Color.White)); var rippleSize = (int)(animationProgress * _tabRects[_baseTabControl.SelectedIndex].Width * 1.75); g.SetClip(_tabRects[_baseTabControl.SelectedIndex]); g.FillEllipse(rippleBrush, new Rectangle(_animationSource.X - rippleSize / 2, _animationSource.Y - rippleSize / 2, rippleSize, rippleSize)); g.ResetClip(); rippleBrush.Dispose(); } //Draw tab headers foreach (TabPage tabPage in _baseTabControl.TabPages) { var currentTabIndex = _baseTabControl.TabPages.IndexOf(tabPage); Brush textBrush = new SolidBrush(Color.FromArgb(CalculateTextAlpha(currentTabIndex, animationProgress), SkinManager.ColorScheme.TextColor)); g.DrawString(tabPage.Text.ToUpper(), SkinManager.ROBOTO_MEDIUM_10, textBrush, _tabRects[currentTabIndex], new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }); textBrush.Dispose(); } //Animate tab indicator var previousSelectedTabIndexIfHasOne = _previousSelectedTabIndex == -1 ? _baseTabControl.SelectedIndex : _previousSelectedTabIndex; var previousActiveTabRect = _tabRects[previousSelectedTabIndexIfHasOne]; var activeTabPageRect = _tabRects[_baseTabControl.SelectedIndex]; var y = activeTabPageRect.Bottom - 2; var x = previousActiveTabRect.X + (int)((activeTabPageRect.X - previousActiveTabRect.X) * animationProgress); var width = previousActiveTabRect.Width + (int)((activeTabPageRect.Width - previousActiveTabRect.Width) * animationProgress); g.FillRectangle(SkinManager.ColorScheme.AccentBrush, x, y, width, TAB_INDICATOR_HEIGHT); } private int CalculateTextAlpha(int tabIndex, double animationProgress) { int primaryA = SkinManager.ACTION_BAR_TEXT.A; int secondaryA = SkinManager.ACTION_BAR_TEXT_SECONDARY.A; if (tabIndex == _baseTabControl.SelectedIndex && !_animationManager.IsAnimating()) { return primaryA; } if (tabIndex != _previousSelectedTabIndex && tabIndex != _baseTabControl.SelectedIndex) { return secondaryA; } if (tabIndex == _previousSelectedTabIndex) { return primaryA - (int)((primaryA - secondaryA) * animationProgress); } return secondaryA + (int)((primaryA - secondaryA) * animationProgress); } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (_tabRects == null) UpdateTabRects(); for (var i = 0; i < _tabRects.Count; i++) { if (_tabRects[i].Contains(e.Location)) { _baseTabControl.SelectedIndex = i; } } _animationSource = e.Location; } private void UpdateTabRects() { _tabRects = new List(); //If there isn't a base tab control, the rects shouldn't be calculated //If there aren't tab pages in the base tab control, the list should just be empty which has been set already; exit the void if (_baseTabControl == null || _baseTabControl.TabCount == 0) return; //Calculate the bounds of each tab header specified in the base tab control using (var b = new Bitmap(1, 1)) { using (var g = Graphics.FromImage(b)) { _tabRects.Add(new Rectangle(SkinManager.FORM_PADDING, 0, TAB_HEADER_PADDING * 2 + (int)g.MeasureString(_baseTabControl.TabPages[0].Text, SkinManager.ROBOTO_MEDIUM_10).Width, Height)); for (int i = 1; i < _baseTabControl.TabPages.Count; i++) { _tabRects.Add(new Rectangle(_tabRects[i - 1].Right, 0, TAB_HEADER_PADDING * 2 + (int)g.MeasureString(_baseTabControl.TabPages[i].Text, SkinManager.ROBOTO_MEDIUM_10).Width, Height)); } } } } } }