diff --git a/src/Eto.Gtk/Forms/Controls/DocumentPageHandler.cs b/src/Eto.Gtk/Forms/Controls/DocumentPageHandler.cs index d769a608e..286427366 100644 --- a/src/Eto.Gtk/Forms/Controls/DocumentPageHandler.cs +++ b/src/Eto.Gtk/Forms/Controls/DocumentPageHandler.cs @@ -89,6 +89,8 @@ public bool Closable set { closeButton.Visible = value; } } + public bool HasUnsavedChanges { get; set; } + public Image Image { get { return image; } diff --git a/src/Eto/Forms/Controls/DocumentPage.cs b/src/Eto/Forms/Controls/DocumentPage.cs index b2699d228..0408a7cf3 100644 --- a/src/Eto/Forms/Controls/DocumentPage.cs +++ b/src/Eto/Forms/Controls/DocumentPage.cs @@ -1,4 +1,4 @@ -namespace Eto.Forms; +namespace Eto.Forms; /// /// Control for a page in a @@ -77,6 +77,16 @@ public bool Closable set { Handler.Closable = value; } } + /// + /// Gets or sets a value indicating whether this has unsaved changes. + /// + /// true if page has unsaved changes; otherwise, false. + public bool HasUnsavedChanges + { + get { return Handler.HasUnsavedChanges; } + set { Handler.HasUnsavedChanges = value; } + } + /// /// Gets or sets the image of the page. /// @@ -113,6 +123,12 @@ public string Text /// true if closable; otherwise, false. bool Closable { get; set; } + /// + /// Gets or sets a value indicating whether this has unsaved changes. + /// + /// true if page has unsaved changes; otherwise, false. + bool HasUnsavedChanges { get; set; } + /// /// Gets or sets the image of the page. /// diff --git a/src/Eto/Forms/ThemedControls/ThemedDocumentControlHandler.cs b/src/Eto/Forms/ThemedControls/ThemedDocumentControlHandler.cs index b4ba694bd..964344010 100644 --- a/src/Eto/Forms/ThemedControls/ThemedDocumentControlHandler.cs +++ b/src/Eto/Forms/ThemedControls/ThemedDocumentControlHandler.cs @@ -33,6 +33,7 @@ public class ThemedDocumentControlHandler : ThemedContainerHandler + /// Gets or sets the background color for the unsaved changes indicator. + /// + /// The background color for the unsaved changes indicator. + public Color UnsavedBackgroundColor + { + get { return unsavedBackgroundColor; } + set + { + unsavedBackgroundColor = value; + tabDrawable.Invalidate(); + } + } + /// /// Gets or sets a value indicating whether to use a fixed tab height. /// @@ -298,6 +313,7 @@ public ThemedDocumentControlHandler() tabForegroundColor = SystemColors.ControlText; tabHighlightForegroundColor = SystemColors.HighlightText; tabHoverForegroundColor = SystemColors.HighlightText; + unsavedBackgroundColor = SystemColors.ControlText; tabDrawable = new Drawable(); @@ -680,33 +696,35 @@ void Drawable_Paint(object sender, PaintEventArgs e) } } - void CalculateTab(ThemedDocumentPageHandler tab, int i, ref float posx) + void CalculateTab(ThemedDocumentPageHandler tab, int i, ref float posX) { - var tabPadding = TabPadding; - var textSize = string.IsNullOrEmpty(tab.Text) ? Size.Empty : Size.Ceiling(Font.MeasureString(tab.Text)); - var size = textSize; - var prevnextsel = mousePos.X > nextPrevWidth || i == -1; - var textoffset = 0; - if (tab.Image != null) + var tabLocation = new PointF(posX, 0); + if (i == selectedIndex && draggingLocation != null) { - textoffset = maxImageSize.Width + tabPadding.Left; - size.Width += textoffset; + tabLocation.Offset(mousePos.X - draggingLocation.Value.X, 0); } - var closesize = (int)Math.Floor(tabDrawable.Height * 0.6); - var tabRect = new RectangleF(posx, 0, size.Width + (tab.Closable ? closesize + tabPadding.Horizontal + tabPadding.Right : tabPadding.Horizontal), tabDrawable.Height); + var imageLocation = new PointF(tabLocation.X + TabPadding.Left, (tabDrawable.Height - maxImageSize.Height) / 2f); + var imageSize = tab.Image is null ? Size.Empty : maxImageSize; + tab.ImageRect = new RectangleF(imageLocation, imageSize); - if (i == selectedIndex && draggingLocation != null) - { - tabRect.Offset(mousePos.X - draggingLocation.Value.X, 0); - } + var textSize = string.IsNullOrEmpty(tab.Text) ? Size.Empty : Size.Ceiling(Font.MeasureString(tab.Text)); + var textLocation = new PointF(tab.ImageRect.Right + (imageSize.IsEmpty ? 0 : TabPadding.Left), (tabDrawable.Height - textSize.Height) / 2); + tab.TextRect = new RectangleF(textLocation, textSize); - tab.Rect = tabRect; + var unsavedSize = tab.HasUnsavedChanges ? new Size(6, 6) : Size.Empty; + var unsavedLocation = new PointF(tab.TextRect.Right + (tab.HasUnsavedChanges ? TabPadding.Right : 0), (tabDrawable.Height - unsavedSize.Height) / 2); + tab.UnsavedRect = new RectangleF(unsavedLocation, unsavedSize); - tab.CloseRect = new RectangleF(tabRect.X + tab.Rect.Width - tabPadding.Right - closesize, (tabDrawable.Height - closesize) / 2, closesize, closesize); - tab.TextRect = new RectangleF(tabRect.X + tabPadding.Left + textoffset, (tabDrawable.Height - size.Height) / 2, textSize.Width, textSize.Height); + var closeSize = tab.Closable + ? (int)Math.Floor(tabDrawable.Height * 0.6) + : 0; + var closeLocation = new PointF(tab.UnsavedRect.Right + (tab.Closable ? TabPadding.Right : 0), (tabDrawable.Height - closeSize) / 2); + tab.CloseRect = new RectangleF(closeLocation, new SizeF(closeSize, closeSize)); - posx += tab.Rect.Width; + tab.Rect = new RectangleF(tabLocation, new SizeF(tab.CloseRect.Right + TabPadding.Right - tabLocation.X, tabDrawable.Height)); + + posX += tab.Rect.Width; } bool IsCloseSelected(ThemedDocumentPageHandler tab) @@ -718,11 +736,6 @@ bool IsCloseSelected(ThemedDocumentPageHandler tab) void DrawTab(Graphics g, ThemedDocumentPageHandler tab, int i) { var prevnextsel = mousePos.X > nextPrevWidth || i == -1; - var closeSelected = IsCloseSelected(tab); - var tabRect = tab.Rect; - var textRect = tab.TextRect; - var closerect = tab.CloseRect; - var closemargin = closerect.Height / 4; var textcolor = Enabled ? TabForegroundColor : DisabledForegroundColor; var backcolor = TabBackgroundColor; @@ -731,33 +744,47 @@ void DrawTab(Graphics g, ThemedDocumentPageHandler tab, int i) textcolor = Enabled ? TabHighlightForegroundColor : DisabledForegroundColor; backcolor = TabHighlightBackgroundColor; } - else if (draggingLocation == null && tabRect.Contains(mousePos) && prevnextsel && Enabled) + else if (draggingLocation == null && tab.Rect.Contains(mousePos) && prevnextsel && Enabled) { textcolor = TabHoverForegroundColor; backcolor = TabHoverBackgroundColor; } - g.FillRectangle(backcolor, tabRect); - if (tab.Image != null) + g.FillRectangle(backcolor, tab.Rect); + g.DrawText(Font, textcolor, tab.TextRect.Location, tab.Text); + + if (tab.Image is not null) { g.SaveTransform(); g.ImageInterpolation = ImageInterpolation.High; - g.DrawImage(tab.Image, tabRect.X + TabPadding.Left, (tabDrawable.Height - maxImageSize.Height) / 2, maxImageSize.Width, maxImageSize.Height); + g.DrawImage(tab.Image, tab.ImageRect); g.RestoreTransform(); } - g.DrawText(Font, textcolor, textRect.Location, tab.Text); + + if (tab.HasUnsavedChanges) + { + g.FillEllipse(UnsavedBackgroundColor, tab.UnsavedRect); + } if (tab.Closable) { + var closeSelected = IsCloseSelected(tab); + var closeBackground = closeSelected ? CloseHighlightBackgroundColor : CloseBackgroundColor; if (closeCornerRadius > 0) - g.FillPath(closeBackground, GraphicsPath.GetRoundRect(closerect, closeCornerRadius)); + g.FillPath(closeBackground, GraphicsPath.GetRoundRect(tab.CloseRect, closeCornerRadius)); else - g.FillRectangle(closeBackground, closerect); + g.FillRectangle(closeBackground, tab.CloseRect); + var closeMargin = (int)tab.CloseRect.Height / 4; + var closeForegroundRect = RectangleF.Inset(tab.CloseRect, new PaddingF(closeMargin)); var closeForeground = Enabled ? closeSelected ? CloseHighlightForegroundColor : CloseForegroundColor : DisabledForegroundColor; - g.DrawLine(closeForeground, closerect.X + closemargin, closerect.Y + closemargin, closerect.X + closerect.Width - 1 - closemargin, closerect.Y + closerect.Height - 1 - closemargin); - g.DrawLine(closeForeground, closerect.X + closemargin, closerect.Y + closerect.Height - 1 - closemargin, closerect.X + closerect.Width - 1 - closemargin, closerect.Y + closemargin); + + g.SaveTransform(); + g.PixelOffsetMode = PixelOffsetMode.Half; + g.DrawLine(closeForeground, closeForegroundRect.TopLeft, closeForegroundRect.BottomRight); + g.DrawLine(closeForeground, closeForegroundRect.TopRight, closeForegroundRect.BottomLeft); + g.RestoreTransform(); } } diff --git a/src/Eto/Forms/ThemedControls/ThemedDocumentPageHandler.cs b/src/Eto/Forms/ThemedControls/ThemedDocumentPageHandler.cs index 117986da2..73a1c87a9 100644 --- a/src/Eto/Forms/ThemedControls/ThemedDocumentPageHandler.cs +++ b/src/Eto/Forms/ThemedControls/ThemedDocumentPageHandler.cs @@ -9,6 +9,7 @@ public class ThemedDocumentPageHandler : ThemedContainerHandler /// Initializes a new instance of the class. @@ -105,6 +106,21 @@ public string Text } } + + /// + /// Gets or sets a value indicating whether this has unsaved changes. + /// + /// true if page has unsaved changes; otherwise, false. + public bool HasUnsavedChanges + { + get { return hasUnsavedChanges; } + set + { + hasUnsavedChanges = value; + Update(); + } + } + /// /// Gets a value indicating whether /// /// events are propagated to the inner control @@ -113,9 +129,13 @@ public string Text internal RectangleF Rect { get; set; } + internal RectangleF UnsavedRect { get; set; } + internal RectangleF CloseRect { get; set; } internal RectangleF TextRect { get; set; } + internal RectangleF ImageRect { get; set; } + void Update() => DocControl?.Update(this); } \ No newline at end of file