/// <summary>Callback that is invoked whenever anything is added or removed from <see cref="Tabs" /> so that we can trigger a redraw of the tabs.</summary> /// <param name="sender">Object for which this event was raised.</param> /// <param name="e">Arguments associated with the event.</param> private void _tabs_CollectionModified(object sender, NotifyCollectionChangedEventArgs e) { SetFrameSize(); if (e.Action == NotifyCollectionChangedAction.Add) { for (int i = 0; i < e.NewItems.Count; i++) { TitleBarTabItem currentTab = Tabs[i + e.NewStartingIndex]; currentTab.Content.TextChanged += Content_TextChanged; currentTab.Closing += TitleBarTabs_Closing; if (AeroPeekEnabled) { TaskbarManager.Instance.TabbedThumbnail.SetActiveTab(CreateThumbnailPreview(currentTab)); } } } if (_overlay != null) { _overlay.Render(true); } }
/// <summary> /// Creates a new thumbnail for <paramref name="tab" /> when the application is initially enabled for AeroPeek or when it is turned on sometime during /// execution. /// </summary> /// <param name="tab">Tab that we are to create the thumbnail for.</param> /// <returns>Thumbnail created for <paramref name="tab" />.</returns> protected virtual TabbedThumbnail CreateThumbnailPreview(TitleBarTabItem tab) { TabbedThumbnail preview = TaskbarManager.Instance.TabbedThumbnail.GetThumbnailPreview(tab.Content); if (preview != null) { TaskbarManager.Instance.TabbedThumbnail.RemoveThumbnailPreview(tab.Content); } preview = new TabbedThumbnail(Handle, tab.Content) { Title = tab.Content.Text, Tooltip = tab.Content.Text }; preview.SetWindowIcon((Icon)tab.Content.Icon.Clone()); preview.TabbedThumbnailActivated += preview_TabbedThumbnailActivated; preview.TabbedThumbnailClosed += preview_TabbedThumbnailClosed; preview.TabbedThumbnailBitmapRequested += preview_TabbedThumbnailBitmapRequested; preview.PeekOffset = new Vector(Padding.Left, Padding.Top - 1); TaskbarManager.Instance.TabbedThumbnail.AddThumbnailPreview(preview); return(preview); }
protected void HandleApplicationMouseLUp(Point mousePos) { // If we released the mouse button while we were dragging a torn tab, put that tab into a new window if (m_tornTab != null) { TitleBarTabItem tabToRelease = null; lock (m_tornTabLock) { if (m_tornTab != null) { tabToRelease = m_tornTab; m_tornTab = null; } } if (tabToRelease != null) { TitleBarTabMainForm newWindow = (TitleBarTabMainForm)Activator.CreateInstance(m_parentForm.GetType()); // Set the initial window position and state properly if (newWindow.WindowState == FormWindowState.Maximized) { Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); newWindow.StartPosition = FormStartPosition.Manual; newWindow.WindowState = FormWindowState.Normal; newWindow.Left = screen.WorkingArea.Left; newWindow.Top = screen.WorkingArea.Top; newWindow.Width = screen.WorkingArea.Width; newWindow.Height = screen.WorkingArea.Height; } else { newWindow.Left = Cursor.Position.X; newWindow.Top = Cursor.Position.Y; } tabToRelease.Parent = newWindow; m_parentForm.ApplicationContext.OpenWindow(newWindow); newWindow.Show(); newWindow.Tabs.Add(tabToRelease); newWindow.SelectedTabIndex = 0; newWindow.ResizeTabContents(); m_tornTabForm.Close(); m_tornTabForm = null; if (m_parentForm.Tabs.Count == 0) { m_parentForm.Close(); } } } OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); }
/// <summary>Calls <see cref="CreateTab" />, adds the resulting tab to the <see cref="Tabs" /> collection, and activates it.</summary> public virtual void AddNewTab() { TitleBarTabItem newTab = CreateTab(); Tabs.Add(newTab); ResizeTabContents(newTab); SelectedTabIndex = _tabs.Count - 1; }
/// <summary>Constructor; initializes the window and constructs the tab thumbnail image to use when dragging.</summary> /// <param name="tab">Tab that was torn out of its parent window.</param> /// <param name="tabRenderer">Renderer instance to use when drawing the actual tab.</param> public TitleBarTornTabForm(TitleBarTabItem tab, BaseTabRenderer tabRenderer, IMessageFilter messageFilter) { m_messageFilter = messageFilter; m_layeredWindow = new LayeredWindow(); m_initialized = false; // Set drawing styles SetStyle(ControlStyles.DoubleBuffer, true); // This should show up as a semi-transparent borderless window Opacity = 0.70; ShowInTaskbar = false; FormBorderStyle = FormBorderStyle.None; // ReSharper disable DoNotCallOverridableMethodsInConstructor BackColor = Color.Fuchsia; // ReSharper restore DoNotCallOverridableMethodsInConstructor TransparencyKey = Color.Fuchsia; AllowTransparency = true; Disposed += TornTabForm_Disposed; // Get the tab thumbnail (full size) and then draw the actual representation of the tab onto it as well Bitmap tabContents = tab.GetImage(); Bitmap contentsAndTab = new Bitmap(tabContents.Width, tabContents.Height + tabRenderer.TabHeight, tabContents.PixelFormat); Graphics tabGraphics = Graphics.FromImage(contentsAndTab); tabGraphics.DrawImage(tabContents, 0, tabRenderer.TabHeight); bool oldShowAddButton = tabRenderer.ShowAddButton; tabRenderer.ShowAddButton = false; tabRenderer.Render( new ListWithEvents <TitleBarTabItem> { tab }, tabGraphics, new Point(0, 0), new Point(0, 0), true); tabRenderer.ShowAddButton = oldShowAddButton; // Scale the thumbnail down to half size m_tabThumbnail = new Bitmap(contentsAndTab.Width / 2, contentsAndTab.Height / 2, contentsAndTab.PixelFormat); Graphics thumbnailGraphics = Graphics.FromImage(m_tabThumbnail); thumbnailGraphics.InterpolationMode = InterpolationMode.High; thumbnailGraphics.CompositingQuality = CompositingQuality.HighQuality; thumbnailGraphics.SmoothingMode = SmoothingMode.AntiAlias; thumbnailGraphics.DrawImage(contentsAndTab, 0, 0, m_tabThumbnail.Width, m_tabThumbnail.Height); Width = m_tabThumbnail.Width - 1; Height = m_tabThumbnail.Height - 1; m_cursorOffset = new Point(tabRenderer.TabContentWidth / 4, tabRenderer.TabHeight / 4); SetWindowPosition(Cursor.Position); }
/// <summary>Checks to see if the <paramref name="cursor" /> is over the <see cref="TitleBarTabItem.CloseButtonArea" /> of the given <paramref name="tab" />.</summary> /// <param name="tab">The tab whose <see cref="TitleBarTabItem.CloseButtonArea" /> we are to check to see if it contains <paramref name="cursor" />.</param> /// <param name="cursor">Current position of the cursor.</param> /// <returns>True if the <paramref name="tab" />'s <see cref="TitleBarTabItem.CloseButtonArea" /> contains <paramref name="cursor" />, false otherwise.</returns> public virtual bool IsOverCloseButton(TitleBarTabItem tab, Point cursor) { if (!tab.ShowCloseButton || _wasTabRepositioning) { return(false); } Rectangle absoluteCloseButtonArea = new Rectangle( tab.Area.X + tab.CloseButtonArea.X, tab.Area.Y + tab.CloseButtonArea.Y, tab.CloseButtonArea.Width, tab.CloseButtonArea.Height); return(absoluteCloseButtonArea.Contains(cursor)); }
/// <summary>Resizes the <see cref="TitleBarTabItem.Content" /> form of the <paramref name="tab" /> to match the size of the client area for this window.</summary> /// <param name="tab">Tab whose <see cref="TitleBarTabItem.Content" /> form we should resize; if not specified, we default to /// <see cref="SelectedTab" />.</param> public void ResizeTabContents(TitleBarTabItem tab = null) { if (tab == null) { tab = SelectedTab; } if (tab != null) { tab.Content.Location = new Point(0, Padding.Top - 1); tab.Content.Size = new Size(ClientRectangle.Width, ClientRectangle.Height - Padding.Top + 1); } }
/// <summary> /// Callback for the <see cref="TabSelected" /> event. Called when a <see cref="TitleBarTabItem" /> gains focus. Sets the active window in Aero Peek via a /// call to <see cref="TabbedThumbnailManager.SetActiveTab(Control)" />. /// </summary> /// <param name="e">Arguments associated with the event.</param> protected void OnTabSelected(TitleBarTabEventArgs e) { if (SelectedTabIndex != -1 && _previews.ContainsKey(SelectedTab.Content) && AeroPeekEnabled) { TaskbarManager.Instance.TabbedThumbnail.SetActiveTab(SelectedTab.Content); } _previousActiveTab = SelectedTab; if (TabSelected != null) { TabSelected(this, e); } }
/// <summary> /// Event handler that is called when a tab's <see cref="TitleBarTabItem.Closing" /> event is fired, which removes the tab from <see cref="Tabs" /> and /// re-renders <see cref="_overlay" />. /// </summary> /// <param name="sender">Object from which this event originated (the <see cref="TitleBarTabItem" /> in this case).</param> /// <param name="e">Arguments associated with the event.</param> private void TitleBarTabs_Closing(object sender, CancelEventArgs e) { TitleBarTabItem tab = (TitleBarTabItem)sender; CloseTab(tab); if (!tab.Content.IsDisposed && AeroPeekEnabled) { TaskbarManager.Instance.TabbedThumbnail.RemoveThumbnailPreview(tab.Content); } if (_overlay != null) { _overlay.Render(true); } }
/// <summary>Generate a new thumbnail image for <paramref name="tab" />.</summary> /// <param name="tab">Tab that we need to generate a thumbnail for.</param> protected void UpdateTabThumbnail(TitleBarTabItem tab) { TabbedThumbnail preview = TaskbarManager.Instance.TabbedThumbnail.GetThumbnailPreview(tab.Content); if (preview == null) { return; } Bitmap bitmap = TabbedThumbnailScreenCapture.GrabWindowBitmap(tab.Content.Handle, tab.Content.Size); preview.SetImage(bitmap); // If we already had a preview image for the tab, dispose of it if (_previews.ContainsKey(tab.Content) && _previews[tab.Content] != null) { _previews[tab.Content].Dispose(); } _previews[tab.Content] = bitmap; }
/// <summary> /// When a child tab updates its <see cref="Form.Icon"/> property, it should call this method to update the icon in the AeroPeek preview. /// </summary> /// <param name="tab">Tab whose icon was updated.</param> /// <param name="icon">The new icon to use. If this is left as null, we use <see cref="Form.Icon"/> on <paramref name="tab"/>.</param> public virtual void UpdateThumbnailPreviewIcon(TitleBarTabItem tab, Icon icon = null) { if (!AeroPeekEnabled) { return; } TabbedThumbnail preview = TaskbarManager.Instance.TabbedThumbnail.GetThumbnailPreview(tab.Content); if (preview == null) { return; } if (icon == null) { icon = tab.Content.Icon; } preview.SetWindowIcon((Icon)icon.Clone()); }
/// <summary> /// Called when a torn tab is dragged into the <see cref="TitleBarTabMainForm.TabDropArea" /> of <see cref="_parentWindow" />. Places the tab in the list and /// sets <see cref="IsTabRepositioning" /> to true to simulate the user continuing to drag the tab around in the window. /// </summary> /// <param name="tab">Tab that was dragged into this window.</param> /// <param name="cursorLocation">Location of the user's cursor.</param> internal virtual void CombineTab(TitleBarTabItem tab, Point cursorLocation) { // Stop rendering to prevent weird stuff from happening like the wrong tab being focused _suspendRendering = true; // Find out where to insert the tab in the list int dropIndex = _parentWindow.Tabs.FindIndex(t => t.Area.Left <= cursorLocation.X && t.Area.Right >= cursorLocation.X); // Simulate the user having clicked in the middle of the tab when they started dragging it so that the tab will move correctly within the window // when the user continues to move the mouse if (_parentWindow.Tabs.Count > 0) { _tabClickOffset = _parentWindow.Tabs.First().Area.Width / 2; } else { _tabClickOffset = 0; } IsTabRepositioning = true; tab.Parent = _parentWindow; if (dropIndex == -1) { _parentWindow.Tabs.Add(tab); dropIndex = _parentWindow.Tabs.Count - 1; } else { _parentWindow.Tabs.Insert(dropIndex, tab); } // Resume rendering _suspendRendering = false; _parentWindow.SelectedTabIndex = dropIndex; _parentWindow.ResizeTabContents(); }
/// <summary>Removes <paramref name="closingTab" /> from <see cref="Tabs" /> and selects the next applicable tab in the list.</summary> /// <param name="closingTab">Tab that is being closed.</param> protected virtual void CloseTab(TitleBarTabItem closingTab) { int removeIndex = Tabs.IndexOf(closingTab); int selectedTabIndex = SelectedTabIndex; Tabs.Remove(closingTab); if (selectedTabIndex > removeIndex) { SelectedTabIndex = selectedTabIndex - 1; } else if (selectedTabIndex == removeIndex) { SelectedTabIndex = Math.Min(selectedTabIndex, Tabs.Count - 1); } else { SelectedTabIndex = selectedTabIndex; } if (_previews.ContainsKey(closingTab.Content)) { _previews[closingTab.Content].Dispose(); _previews.Remove(closingTab.Content); } if (_previousActiveTab != null && closingTab.Content == _previousActiveTab.Content) { _previousActiveTab = null; } if (Tabs.Count == 0 && ExitOnLastTabClose) { Close(); } }
/// <summary> /// Called from the <see cref="_parentWindow" /> to determine which, if any, of the <paramref name="tabs" /> the <paramref name="cursor" /> is /// over. /// </summary> /// <param name="tabs">The list of tabs that we should check.</param> /// <param name="cursor">The relative position of the cursor within the window.</param> /// <returns>The tab within <paramref name="tabs" /> that the <paramref name="cursor" /> is over; if none, then null is returned.</returns> public virtual TitleBarTabItem OverTab(IEnumerable <TitleBarTabItem> tabs, Point cursor) { TitleBarTabItem overTab = null; foreach (TitleBarTabItem tab in tabs.Where(tab => tab.TabImage != null)) { // We have to loop through each of the tabs in turn and check their status; if the tabs overlap, then their areas overlap as well, which means // that we may find see that the cursor is over an inactive tab, but we need to check the active tabs as well, since they may overlap their // areas and take precedence. if (tab.Active && IsOverTab(tab, cursor)) { overTab = tab; break; } if (IsOverTab(tab, cursor)) { overTab = tab; } } return(overTab); }
protected void HandleApplicationMouseMove(Point mousePos) { bool reRender = false; if (m_tornTab != null && m_dropAreas != null) { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < m_dropAreas.Length; i++) // ReSharper restore ForCanBeConvertedToForeach { // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area if (m_dropAreas[i].Item2.Contains(mousePos)) { TitleBarTabItem tabToCombine = null; lock (m_tornTabLock) { if (m_tornTab != null) { tabToCombine = m_tornTab; m_tornTab = null; } } if (tabToCombine != null) { int i1 = i; // In all cases where we need to affect the UI, we call Invoke so that those changes are made on the main UI thread since // we are on a separate processing thread in this case m_dropAreas[i1].Item1.TabRenderer.CombineTab(tabToCombine, mousePos); tabToCombine = null; m_tornTabForm.Close(); m_tornTabForm = null; if (m_parentForm.Tabs.Count == 0) { m_parentForm.Close(); } } } } } else if (!m_parentForm.TabRenderer.IsTabRepositioning) { // If we were over a close button previously, check to see if the cursor is still over that tab's // close button; if not, re-render if (m_isOverCloseButtonForTab != -1 && (m_isOverCloseButtonForTab >= m_parentForm.Tabs.Count || !m_parentForm.TabRenderer.IsOverCloseButton(m_parentForm.Tabs[m_isOverCloseButtonForTab], GetRelativeCursorPosition(mousePos)))) { reRender = true; m_isOverCloseButtonForTab = -1; } // Otherwise, see if any tabs' close button is being hovered over else { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < m_parentForm.Tabs.Count; i++) // ReSharper restore ForCanBeConvertedToForeach { if (m_parentForm.TabRenderer.IsOverCloseButton(m_parentForm.Tabs[i], GetRelativeCursorPosition(mousePos))) { m_isOverCloseButtonForTab = i; reRender = true; break; } } } } else { m_wasDragging = true; // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the // TabTearDragDistance setting Rectangle dragArea = TabDropArea; dragArea.Inflate(m_parentForm.TabRenderer.TabTearDragDistance, m_parentForm.TabRenderer.TabTearDragDistance); // If the cursor is outside the tear area, tear it away from the current window if (!dragArea.Contains(mousePos) && m_tornTab == null) { lock (m_tornTabLock) { if (m_tornTab == null) { m_parentForm.TabRenderer.IsTabRepositioning = false; // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging m_tornTab = m_parentForm.SelectedTab; m_tornTab.ClearSubscriptions(); m_tornTabForm = new TitleBarTornTabForm(m_tornTab, m_parentForm.TabRenderer, this); } } if (m_tornTab != null) { m_parentForm.SelectedTabIndex = (m_parentForm.SelectedTabIndex == m_parentForm.Tabs.Count - 1 ? m_parentForm.SelectedTabIndex - 1 : m_parentForm.SelectedTabIndex + 1); m_parentForm.Tabs.Remove(m_tornTab); // If this tab was the only tab in the window, hide the parent window if (m_parentForm.Tabs.Count == 0) { m_parentForm.Hide(); } m_tornTabForm.Show(); m_dropAreas = (from window in m_parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) select new Tuple <TitleBarTabMainForm, Rectangle>(window, window.TabDropArea)).ToArray(); } } } OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, mousePos.X, mousePos.Y, 0)); if (m_parentForm.TabRenderer.IsTabRepositioning) { reRender = true; } if (reRender) { Render(mousePos, true); } }
/// <summary>Overrides the message pump for the window so that we can respond to click events on the tabs themselves.</summary> /// <param name="m">Message received by the pump.</param> protected override void WndProc(ref Message m) { switch ((WM)m.Msg) { case WM.WM_NCPAINT: case WM.WM_PAINT: // If any of children need to be drawn, we need to draw title bar as well Render(); System.Diagnostics.Debug.Print("Paint"); break; case WM.WM_NCLBUTTONDOWN: case WM.WM_LBUTTONDOWN: Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); // If we were over a tab, set the capture state for the window so that we'll actually receive a WM_LBUTTONUP message if (m_parentForm.TabRenderer.OverTab(m_parentForm.Tabs, relativeCursorPosition) == null && !m_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) { m_parentForm.ForwardMessage(ref m); } else { // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released TitleBarTabItem clickedTab = m_parentForm.TabRenderer.OverTab(m_parentForm.Tabs, relativeCursorPosition); if (clickedTab != null) { // If the user clicked the close button, remove the tab from the list if (!m_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition)) { m_parentForm.ResizeTabContents(clickedTab); m_parentForm.SelectedTabIndex = m_parentForm.Tabs.IndexOf(clickedTab); Render(); } OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); } m_parentForm.Activate(); } break; case WM.WM_LBUTTONDBLCLK: m_parentForm.ForwardMessage(ref m); break; // We always return HTCAPTION for the hit test message so that the underlying window doesn't have its focus removed case WM.WM_NCHITTEST: m.Result = new IntPtr((int)HT.HTCAPTION); break; case WM.WM_LBUTTONUP: case WM.WM_NCLBUTTONUP: Point relativeCursorPosition2 = GetRelativeCursorPosition(Cursor.Position); if (m_parentForm.TabRenderer.OverTab(m_parentForm.Tabs, relativeCursorPosition2) == null && !m_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) { m_parentForm.ForwardMessage(ref m); } else { // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released TitleBarTabItem clickedTab = m_parentForm.TabRenderer.OverTab(m_parentForm.Tabs, relativeCursorPosition2); if (clickedTab != null) { // If the user clicked the close button, remove the tab from the list if (m_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition2)) { clickedTab.Content.Close(); Render(); } else { m_parentForm.OnTabClicked( new TitleBarTabEventArgs { Tab = clickedTab, TabIndex = m_parentForm.SelectedTabIndex, Action = TabControlAction.Selected, WasDragging = m_wasDragging }); } } // Otherwise, if the user clicked the add button, call CreateTab to add a new tab to the list and select it else if (m_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) { m_parentForm.AddNewTab(); } OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); } break; default: base.WndProc(ref m); break; } }
/// <summary>Tests whether the <paramref name="cursor" /> is hovering over the given <paramref name="tab" />.</summary> /// <param name="tab">Tab that we are to see if the cursor is hovering over.</param> /// <param name="cursor">Current location of the cursor.</param> /// <returns> /// True if the <paramref name="cursor" /> is within the <see cref="TitleBarTabItem.Area" /> of the <paramref name="tab" /> and is over a non- transparent /// pixel of <see cref="TitleBarTabItem.TabImage" />, false otherwise. /// </returns> protected virtual bool IsOverTab(TitleBarTabItem tab, Point cursor) { return(IsOverNonTransparentArea(tab.Area, tab.TabImage, cursor)); }
/// <summary>Renders the list of <paramref name="tabs" /> to the screen using the given <paramref name="graphicsContext" />.</summary> /// <param name="tabs">List of tabs that we are to render.</param> /// <param name="graphicsContext">Graphics context that we should use while rendering.</param> /// <param name="cursor">Current location of the cursor on the screen.</param> /// <param name="forceRedraw">Flag indicating whether or not the redraw should be forced.</param> /// <param name="offset">Offset within <paramref name="graphicsContext" /> that the tabs should be rendered.</param> public virtual void Render(ListWithEvents <TitleBarTabItem> tabs, Graphics graphicsContext, Point offset, Point cursor, bool forceRedraw = false) { if (_suspendRendering) { return; } if (tabs == null || tabs.Count == 0) { return; } Point screenCoordinates = _parentWindow.PointToScreen(_parentWindow.ClientRectangle.Location); // Calculate the maximum tab area, excluding the add button and any minimize/maximize/close buttons in the window _maxTabArea.Location = new Point(SystemInformation.BorderSize.Width + offset.X + screenCoordinates.X, offset.Y + screenCoordinates.Y); _maxTabArea.Width = (_parentWindow.ClientRectangle.Width - offset.X - (ShowAddButton ? _addButtonImage.Width + AddButtonMarginLeft + AddButtonMarginRight : 0) - (tabs.Count() * OverlapWidth) - (_parentWindow.ControlBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MinimizeBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MaximizeBox ? SystemInformation.CaptionButtonSize.Width : 0)); _maxTabArea.Height = _activeCenterImage.Height; // Get the width of the content area for each tab by taking the parent window's client width, subtracting the left and right border widths and the // add button area (if applicable) and then dividing by the number of tabs int tabContentWidth = Math.Min(_activeCenterImage.Width, Convert.ToInt32(Math.Floor(Convert.ToDouble(_maxTabArea.Width / tabs.Count())))); // Determine if we need to redraw the TabImage properties for each tab by seeing if the content width that we calculated above is equal to content // width we had in the previous rendering pass bool redraw = (tabContentWidth != _tabContentWidth || forceRedraw); if (redraw) { _tabContentWidth = tabContentWidth; } int i = tabs.Count - 1; List <Tuple <TitleBarTabItem, Rectangle> > activeTabs = new List <Tuple <TitleBarTabItem, Rectangle> >(); // Render the background image if (_background != null) { graphicsContext.DrawImage(_background, offset.X, offset.Y, _parentWindow.Width, _activeCenterImage.Height); } int selectedIndex = tabs.FindIndex(t => t.Active); if (selectedIndex != -1) { Rectangle tabArea = new Rectangle( SystemInformation.BorderSize.Width + offset.X + (selectedIndex * (tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width - OverlapWidth)), offset.Y, tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width, _activeCenterImage.Height); if (IsTabRepositioning && _tabClickOffset != null) { // Make sure that the user doesn't move the tab past the beginning of the list or the outside of the window tabArea.X = cursor.X - _tabClickOffset.Value; tabArea.X = Math.Max(SystemInformation.BorderSize.Width + offset.X, tabArea.X); tabArea.X = Math.Min( SystemInformation.BorderSize.Width + (_parentWindow.WindowState == FormWindowState.Maximized ? _parentWindow.ClientRectangle.Width - (_parentWindow.ControlBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MinimizeBox ? SystemInformation.CaptionButtonSize.Width : 0) - (_parentWindow.MaximizeBox ? SystemInformation.CaptionButtonSize.Width : 0) : _parentWindow.ClientRectangle.Width) - tabArea.Width, tabArea.X); int dropIndex = 0; // Figure out which slot the active tab is being "dropped" over if (tabArea.X - SystemInformation.BorderSize.Width - offset.X - TabRepositionDragDistance > 0) { dropIndex = Math.Min( Convert.ToInt32( Math.Round( Convert.ToDouble(tabArea.X - SystemInformation.BorderSize.Width - offset.X - TabRepositionDragDistance) / Convert.ToDouble(tabArea.Width - OverlapWidth))), tabs.Count - 1); } // If the tab has been moved over another slot, move the tab object in the window's tab list if (dropIndex != selectedIndex) { TitleBarTabItem tab = tabs[selectedIndex]; _parentWindow.Tabs.SuppressEvents(); _parentWindow.Tabs.Remove(tab); _parentWindow.Tabs.Insert(dropIndex, tab); _parentWindow.Tabs.ResumeEvents(); } } activeTabs.Add(new Tuple <TitleBarTabItem, Rectangle>(tabs[selectedIndex], tabArea)); } // Loop through the tabs in reverse order since we need the ones farthest on the left to overlap those to their right foreach (TitleBarTabItem tab in ((IEnumerable <TitleBarTabItem>)tabs).Reverse()) { Rectangle tabArea = new Rectangle( SystemInformation.BorderSize.Width + offset.X + (i * (tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width - OverlapWidth)), offset.Y, tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width, _activeCenterImage.Height); // If we need to redraw the tab image, null out the property so that it will be recreated in the call to Render() below if (redraw) { tab.TabImage = null; } // In this first pass, we only render the inactive tabs since we need the active tabs to show up on top of everything else if (!tab.Active) { Render(graphicsContext, tab, tabArea, cursor); } i--; } // In the second pass, render all of the active tabs identified in the previous pass foreach (Tuple <TitleBarTabItem, Rectangle> tab in activeTabs) { Render(graphicsContext, tab.Item1, tab.Item2, cursor); } _previousTabCount = tabs.Count; // Render the add tab button to the screen if (ShowAddButton && !IsTabRepositioning) { _addButtonArea = new Rectangle( (_previousTabCount * (tabContentWidth + _activeLeftSideImage.Width + _activeRightSideImage.Width - OverlapWidth)) + _activeRightSideImage.Width + AddButtonMarginLeft + offset.X, AddButtonMarginTop + offset.Y, _addButtonImage.Width, _addButtonImage.Height); bool cursorOverAddButton = IsOverAddButton(cursor); graphicsContext.DrawImage( cursorOverAddButton ? _addButtonHoverImage : _addButtonImage, _addButtonArea, 0, 0, cursorOverAddButton ? _addButtonHoverImage.Width : _addButtonImage.Width, cursorOverAddButton ? _addButtonHoverImage.Height : _addButtonImage.Height, GraphicsUnit.Pixel); } }
/// <summary>Internal method for rendering an individual <paramref name="tab" /> to the screen.</summary> /// <param name="graphicsContext">Graphics context to use when rendering the tab.</param> /// <param name="tab">Individual tab that we are to render.</param> /// <param name="area">Area of the screen that the tab should be rendered to.</param> /// <param name="cursor">Current position of the cursor.</param> protected virtual void Render(Graphics graphicsContext, TitleBarTabItem tab, Rectangle area, Point cursor) { if (_suspendRendering) { return; } // If we need to redraw the tab image if (tab.TabImage == null) { // We render the tab to an internal property so that we don't necessarily have to redraw it in every rendering pass, only if its width or // status have changed tab.TabImage = new Bitmap( area.Width, tab.Active ? _activeCenterImage.Height : _inactiveCenterImage.Height); using (Graphics tabGraphicsContext = Graphics.FromImage(tab.TabImage)) { // Draw the left, center, and right portions of the tab tabGraphicsContext.DrawImage( tab.Active ? _activeLeftSideImage : _inactiveLeftSideImage, new Rectangle( 0, 0, tab.Active ? _activeLeftSideImage .Width : _inactiveLeftSideImage .Width, tab.Active ? _activeLeftSideImage. Height : _inactiveLeftSideImage .Height), 0, 0, tab.Active ? _activeLeftSideImage.Width : _inactiveLeftSideImage.Width, tab.Active ? _activeLeftSideImage.Height : _inactiveLeftSideImage.Height, GraphicsUnit.Pixel); tabGraphicsContext.DrawImage( tab.Active ? _activeCenterImage : _inactiveCenterImage, new Rectangle( (tab.Active ? _activeLeftSideImage. Width : _inactiveLeftSideImage .Width), 0, _tabContentWidth, tab.Active ? _activeCenterImage . Height : _inactiveCenterImage . Height), 0, 0, _tabContentWidth, tab.Active ? _activeCenterImage.Height : _inactiveCenterImage.Height, GraphicsUnit.Pixel); tabGraphicsContext.DrawImage( tab.Active ? _activeRightSideImage : _inactiveRightSideImage, new Rectangle( (tab.Active ? _activeLeftSideImage .Width : _inactiveLeftSideImage .Width) + _tabContentWidth, 0, tab.Active ? _activeRightSideImage .Width : _inactiveRightSideImage .Width, tab.Active ? _activeRightSideImage .Height : _inactiveRightSideImage .Height), 0, 0, tab.Active ? _activeRightSideImage.Width : _inactiveRightSideImage.Width, tab.Active ? _activeRightSideImage.Height : _inactiveRightSideImage. Height, GraphicsUnit.Pixel); // Draw the close button if (tab.ShowCloseButton) { Image closeButtonImage = IsOverCloseButton(tab, cursor) ? _closeButtonHoverImage : _closeButtonImage; tab.CloseButtonArea = new Rectangle( area.Width - (tab.Active ? _activeRightSideImage.Width : _inactiveRightSideImage.Width) - CloseButtonMarginRight - closeButtonImage.Width, CloseButtonMarginTop, closeButtonImage.Width, closeButtonImage.Height); tabGraphicsContext.DrawImage( closeButtonImage, tab.CloseButtonArea, 0, 0, closeButtonImage.Width, closeButtonImage.Height, GraphicsUnit.Pixel); } } tab.Area = area; } // Render the tab's saved image to the screen graphicsContext.DrawImage( tab.TabImage, area, 0, 0, tab.TabImage.Width, tab.TabImage.Height, GraphicsUnit.Pixel); // Render the icon for the tab's content, if it exists and there's room for it in the tab's content area if (tab.Content.ShowIcon && _tabContentWidth > 16 + IconMarginLeft + (tab.ShowCloseButton ? CloseButtonMarginLeft + tab.CloseButtonArea.Width + CloseButtonMarginRight : 0)) { graphicsContext.DrawIcon( new Icon(tab.Content.Icon, 16, 16), new Rectangle(area.X + OverlapWidth + IconMarginLeft, IconMarginTop + area.Y, 16, 16)); } // Render the caption for the tab's content if there's room for it in the tab's content area if (_tabContentWidth > (tab.Content.ShowIcon ? 16 + IconMarginLeft + IconMarginRight : 0) + CaptionMarginLeft + CaptionMarginRight + (tab.ShowCloseButton ? CloseButtonMarginLeft + tab.CloseButtonArea.Width + CloseButtonMarginRight : 0)) { graphicsContext.DrawString( tab.Caption, SystemFonts.CaptionFont, Brushes.Black, new Rectangle( area.X + OverlapWidth + CaptionMarginLeft + (tab.Content.ShowIcon ? IconMarginLeft + 16 + IconMarginRight : 0), CaptionMarginTop + area.Y, _tabContentWidth - (tab.Content.ShowIcon ? IconMarginLeft + 16 + IconMarginRight : 0) - (tab.ShowCloseButton ? _closeButtonImage.Width + CloseButtonMarginRight + CloseButtonMarginLeft : 0), tab.TabImage.Height), new StringFormat(StringFormatFlags.NoWrap) { Trimming = StringTrimming.EllipsisCharacter }); } }