using System; using System.Drawing; using System.Windows.Forms; using System.Collections; using System.Drawing.Drawing2D; using FastReport.Utils; namespace FastReport.Controls { internal class Tab { public FRTabStrip TabControl { get; set; } public string Text { get; set; } public object Tag { get; set; } public Image Image { get; set; } public bool Visible { get; set; } public bool CloseButton { get; set; } public bool AllowMove { get; set; } public Rectangle DisplayRectangle { get; set; } public int CalcWidth() { if (!Visible) return 0; int _2 = TabControl.LogicalToDevice(2); int _16 = TabControl.LogicalToDevice(16); int result = _2; if (Image != null) result += _16; #if AVALONIA TextRenderer.FontScale = (float)TabControl.DpiScale; #endif result += _2 + TextRenderer.MeasureText(Text, TabControl.Font).Width + _2; if (CloseButton) result += _16 + _2; return result; } public Tab() { AllowMove = true; CloseButton = true; Visible = true; } public Tab(string text) : this() { Text = text; } public Tab(string text, Image image) : this(text) { Image = image; } } internal class TabCollection : CollectionBase { private FRTabStrip FOwner; public Tab this[int index] { get { return List[index] as Tab; } set { List[index] = value; } } public void AddRange(Tab[] range) { foreach (Tab t in range) { Add(t); } } public int Add(Tab value) { return List.Add(value); } public void Insert(int index, Tab value) { List.Insert(index, value); } public void Remove(Tab value) { List.Remove(value); } public int IndexOf(Tab value) { return List.IndexOf(value); } public bool Contains(Tab value) { return List.Contains(value); } protected override void OnInsert(int index, Object value) { if (FOwner != null) { (value as Tab).TabControl = FOwner; } } public TabCollection(FRTabStrip owner) { FOwner = owner; } } internal delegate void TabMovedEventHandler(object sender, TabMovedEventArgs e); internal class TabMovedEventArgs { public int OldIndex; public int NewIndex; public TabMovedEventArgs(int oldIndex, int newIndex) { OldIndex = oldIndex; NewIndex = newIndex; } } internal enum TabOrientation { Top, Bottom } internal class FRTabStrip : Control { private int selectedTabIndex; private bool scrollButtonsVisible; private int highlightTabIndex; private bool highlightLeftBtn; private bool highlightRightBtn; private bool highlightCloseButton; private int offset; private int saveTabIndex; private bool movingTab; private bool tabMoved; private int mouseOffset; private TabOrientation tabOrientation; private TabStripColors style = new TabStripColors(); public TabCollection Tabs { get; } public int SelectedTabIndex { get { return selectedTabIndex; } set { if (value < 0) value = -1; if (value > Tabs.Count - 1) value = Tabs.Count - 1; selectedTabIndex = value; EnsureTabVisible(); Refresh(); SelectedTabChanged?.Invoke(this, null); } } public Tab SelectedTab { get { if (SelectedTabIndex == -1 || SelectedTabIndex >= Tabs.Count) return null; return Tabs[SelectedTabIndex]; } set { SelectedTabIndex = Tabs.IndexOf(value); } } public bool AllowTabReorder { get; set; } public TabOrientation TabOrientation { get { return tabOrientation; } set { tabOrientation = value; Refresh(); } } public TabStripColors Style { get { return style; } set { style = value; Refresh(); } } public event EventHandler SelectedTabChanged; public event EventHandler TabClosed; public event TabMovedEventHandler TabMoved; private void EnsureTabVisible() { if (Width == 0) return; Tab tab = SelectedTab; if (tab == null) return; int tabsWidth = CalcItems(); int _4 = this.LogicalToDevice(4); int add = scrollButtonsVisible ? this.LogicalToDevice(40) : 0; if (tab.DisplayRectangle.Left < 0) offset += -tab.DisplayRectangle.Left + _4; else if (tab.DisplayRectangle.Right > Width - add) offset -= tab.DisplayRectangle.Right - (Width - add) + _4; if (offset > 0) offset = 0; if (tabsWidth < DisplayRectangle.Width) offset = 0; Refresh(); } private int CalcItems() { int _3 = this.LogicalToDevice(3); int _4 = this.LogicalToDevice(4); int x = _4 + offset; int tabsWidth = 0; foreach (Tab tab in Tabs) { if (!tab.Visible) continue; int width = tab.CalcWidth(); if (TabOrientation == TabOrientation.Top) tab.DisplayRectangle = new Rectangle(x, _3, width, Height - _3); else tab.DisplayRectangle = new Rectangle(x, 0, width, Height - _4); x += width; tabsWidth += width; } return tabsWidth; } private void HighlightButton(Graphics g, int x, int y) { if (!scrollButtonsVisible) return; int _16 = this.LogicalToDevice(16); using (var brush = new SolidBrush(Color.FromArgb(193, 210, 238))) using (var pen = new Pen(Color.FromArgb(49, 106, 197), this.LogicalToDevice(1f))) { g.FillRectangle(brush, x, y, _16, _16); g.DrawRectangle(pen, x, y, _16, _16); } } private void ScrollLeft() { if (offset < 0) { offset += SelectedTab.DisplayRectangle.Width; if (offset > 0) offset = 0; } Refresh(); } private void ScrollRight() { int tabsWidth = 0; for (int i = 0; i < Tabs.Count - 1; i++) tabsWidth += Tabs[i].DisplayRectangle.Width; if (-offset < tabsWidth) offset -= SelectedTab.DisplayRectangle.Width; Refresh(); } protected override void OnPaint(PaintEventArgs e) { CalcItems(); Graphics g = e.Graphics; Pen pen = new Pen(style.Border, this.LogicalToDevice(1f)); LinearGradientBrush brush = new LinearGradientBrush(DisplayRectangle, style.GradientBegin, style.GradientEnd, LinearGradientMode.Vertical); g.FillRectangle(brush, DisplayRectangle); Brush activeBrush = new SolidBrush(style.ActiveTabBackground); // draw all tabs except selected one int tabsWidth = 0; for (int i = 0; i < Tabs.Count; i++) { Tab tab = Tabs[i]; if (!tab.Visible) continue; Rectangle rect = tab.DisplayRectangle; tabsWidth += rect.Width; if (i != SelectedTabIndex) DrawTab(g, i, pen, activeBrush); } // draw top/bottom line using (Brush lineBrush = new SolidBrush(style.Border)) { int h = this.LogicalToDevice(style.BorderWidth); if (TabOrientation == TabOrientation.Top) g.FillRectangle(lineBrush, 0, Height - h, Width, h); else g.FillRectangle(lineBrush, 0, 0, Width, h); } // draw active tab if (SelectedTabIndex != -1) DrawTab(g, SelectedTabIndex, pen, activeBrush); scrollButtonsVisible = tabsWidth > Width; if (scrollButtonsVisible) DrawScrollButtons(g, brush); pen.Dispose(); brush.Dispose(); activeBrush.Dispose(); } private void DrawTab(Graphics g, int i, Pen pen, Brush brush) { int _1 = this.LogicalToDevice(1); int _2 = this.LogicalToDevice(2); int _3 = this.LogicalToDevice(3); int _4 = this.LogicalToDevice(4); int _5 = this.LogicalToDevice(5); int _16 = this.LogicalToDevice(16); Tab tab = Tabs[i]; Rectangle rect = tab.DisplayRectangle; int x = rect.Left; if (i == SelectedTabIndex || i == highlightTabIndex) { g.FillRectangle(brush, rect.Left, rect.Top, rect.Width, rect.Height); if (TabOrientation == TabOrientation.Top) { g.DrawLines(pen, new Point[] { new Point(rect.Left, rect.Bottom), new Point(rect.Left, rect.Top), new Point(rect.Right, rect.Top), new Point(rect.Right, rect.Bottom) }); } else { g.DrawLines(pen, new Point[] { new Point(rect.Left, rect.Top), new Point(rect.Left, rect.Bottom), new Point(rect.Right, rect.Bottom), new Point(rect.Right, rect.Top) }); } } else if (i < Tabs.Count - 1) { g.DrawLine(pen, rect.Right, rect.Top + _3, rect.Right, rect.Bottom - _3); } if (tab.Image != null) { g.DrawImage(tab.Image, rect.Left + _3, rect.Top + _3); x += _16; } TextRenderer.DrawText(g, tab.Text, Font, new Point(x + _4, rect.Top + _2 + 1), i == SelectedTabIndex || i == highlightTabIndex ? style.ActiveTabForeground : style.InactiveTabForeground); if (tab.CloseButton) DrawCloseButton(g, rect.Right - _16, rect.Top + _4, i); } private void DrawCloseButton(Graphics g, int x, int y, int i) { int btnSize = this.LogicalToDevice(13); int _3 = this.LogicalToDevice(3); bool isHighlight = highlightCloseButton && i == highlightTabIndex; if (isHighlight) { using (var brush = new SolidBrush(Color.FromArgb(196, 43, 28))) g.FillRectangle(brush, new Rectangle(x, y, btnSize, btnSize)); } using (var p = new Pen( isHighlight ? Color.White : i == SelectedTabIndex || i == highlightTabIndex ? style.ActiveTabForeground : style.InactiveTabForeground, this.LogicalToDevice(1f))) { g.DrawLine(p, x + _3, y + _3, x + btnSize - _3 - 1, y + btnSize - _3 - 1); g.DrawLine(p, x + _3, y + btnSize - _3 - 1, x + btnSize - _3 - 1, y + _3); } } private void DrawScrollButtons(Graphics g, Brush brush) { int _4 = this.LogicalToDevice(4); int _20 = this.LogicalToDevice(20); int _22 = this.LogicalToDevice(22); int _38 = this.LogicalToDevice(38); int _40 = this.LogicalToDevice(40); g.FillRectangle(brush, Width - _40, 1, _40, Height - 2); if (highlightLeftBtn) HighlightButton(g, Width - _38, _4); g.DrawImage(this.GetImage(186), Width - _38, _4); if (highlightRightBtn) HighlightButton(g, Width - _20, _4); g.DrawImage(this.GetImage(187), Width - _22, _4); } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); int _6 = this.LogicalToDevice(6); int _16 = this.LogicalToDevice(16); int _20 = this.LogicalToDevice(20); int _24 = this.LogicalToDevice(24); int _38 = this.LogicalToDevice(38); movingTab = false; if (scrollButtonsVisible && e.X > Width - _38 && e.X < Width - _24) { ScrollLeft(); } else if (scrollButtonsVisible && e.X > Width - _20 && e.X < Width - _6) { ScrollRight(); } else { Tab tab = TabAt(e.X); if (tab != null) { if (tab.CloseButton && e.X > tab.DisplayRectangle.Right - _16 && e.X < tab.DisplayRectangle.Right) { // close button, handle in mouseup return; } SelectedTab = tab; movingTab = true; } } saveTabIndex = SelectedTabIndex; tabMoved = false; mouseOffset = 0; } private void CloseTab(Tab tab) { TabClosed?.Invoke(tab, null); } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); int _6 = this.LogicalToDevice(6); int _16 = this.LogicalToDevice(16); int _20 = this.LogicalToDevice(20); int _24 = this.LogicalToDevice(24); int _38 = this.LogicalToDevice(38); if (e.Button == MouseButtons.None) { bool needRefresh = false; if (scrollButtonsVisible) { bool b = e.X > Width - _38 && e.X < Width - _24; if (b != highlightLeftBtn) { highlightLeftBtn = b; needRefresh = true; } b = e.X > Width - _20 && e.X < Width - _6; if (highlightRightBtn != b) { highlightRightBtn = b; needRefresh = true; } } int tabIndex = -1; Tab tab = TabAt(e.X); if (tab != null) { tabIndex = Tabs.IndexOf(tab); bool b = e.X > tab.DisplayRectangle.Right - _16 && e.X < tab.DisplayRectangle.Right; if (b != highlightCloseButton) { highlightCloseButton = b; needRefresh = true; } } if (highlightTabIndex != tabIndex) { highlightTabIndex = tabIndex; needRefresh = true; } if (needRefresh) Refresh(); } else if (e.Button == MouseButtons.Left && movingTab && AllowTabReorder) { Tab oldTab = SelectedTab; Tab newTab = TabAt(e.X + mouseOffset); if (oldTab != null && newTab != null && newTab != oldTab && newTab.AllowMove && oldTab.AllowMove) { int oldIndex = Tabs.IndexOf(oldTab); int newIndex = Tabs.IndexOf(newTab); Tabs.Remove(oldTab); Tabs.Insert(newIndex, oldTab); Refresh(); SelectedTabIndex = newIndex; tabMoved = true; #if !(WPF || AVALONIA) if (oldIndex < newIndex) mouseOffset = oldTab.DisplayRectangle.Left - e.X; else mouseOffset = e.X - oldTab.DisplayRectangle.Right; #endif } } } protected override void OnMouseUp(MouseEventArgs e) { if (tabMoved) { TabMoved?.Invoke(this, new TabMovedEventArgs(saveTabIndex, SelectedTabIndex)); } else { int _16 = this.LogicalToDevice(16); Tab tab = TabAt(e.X); if (tab != null) { if (tab.CloseButton && e.X > tab.DisplayRectangle.Right - _16 && e.X < tab.DisplayRectangle.Right) CloseTab(tab); } } base.OnMouseUp(e); } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); if (highlightLeftBtn || highlightRightBtn || highlightTabIndex != -1) { highlightLeftBtn = false; highlightRightBtn = false; highlightCloseButton = false; highlightTabIndex = -1; Refresh(); } } public Tab TabAt(int x) { foreach (Tab tab in Tabs) { if (!tab.Visible) continue; if (x >= tab.DisplayRectangle.Left && x <= tab.DisplayRectangle.Right) return tab; } return null; } public FRTabStrip() { Tabs = new TabCollection(this); scrollButtonsVisible = true; AllowTabReorder = true; highlightTabIndex = -1; SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); Height = 24; } } }