Got visuals working again

This commit is contained in:
Tigger Kindel
2023-03-16 04:04:18 -06:00
parent 1de683c13a
commit f4aa58ea0a
6 changed files with 231 additions and 164 deletions

View File

@@ -54,7 +54,7 @@ namespace Terminal.Gui {
// We now have rcol/rrow in coordinates relative to our SuperView. If our SuperView has
// a SuperView, keep going...
Parent?.SuperView?.SuperView?.ViewToScreen (rcol, rrow, out rcol, out rrow, clipped);
Parent?.SuperView?.ViewToScreen (rcol, rrow, out rcol, out rrow, clipped);
}
/// <summary>
@@ -83,23 +83,6 @@ namespace Terminal.Gui {
// }
}
/// <inheritdoc/>
public override void OnDrawContent (Rect viewport)
{
if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
Clear (viewport);
SetSubViewNeedsDisplay ();
// Draw any Text
if (TextFormatter != null) {
TextFormatter.NeedsFormat = true;
Rect containerBounds = GetContainerBounds ();
TextFormatter?.Draw (ViewToScreen (viewport), HasFocus ? ColorScheme.Focus : GetNormalColor (),
HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
containerBounds);
}
}
}
/// <summary>
/// Redraws the Frames that comprise the <see cref="Frame"/>.
@@ -116,6 +99,8 @@ namespace Terminal.Gui {
Driver.SetAttribute (ColorScheme.Normal);
}
var prevClip = SetClip (Frame);
var screenBounds = ViewToScreen (Frame);
Thickness.Draw (screenBounds, (string)Data);
@@ -134,9 +119,12 @@ namespace Terminal.Gui {
}
if (!ustring.IsNullOrEmpty (Parent?.Title)) {
Driver.SetAttribute (Parent.HasFocus ? Parent.GetHotNormalColor () : Parent.GetNormalColor ());
Driver.DrawWindowTitle (screenBounds, Parent?.Title, 0, 0, 0, 0);
}
}
Driver.Clip = prevClip;
}
//public Label DiagnosticsLabel { get; set; }

View File

@@ -1188,10 +1188,13 @@ namespace Terminal.Gui {
if (maxBounds.Width == 0 || maxBounds.Height == 0) {
return;
}
var savedClip = Application.Driver?.Clip;
if (Application.Driver != null) {
Application.Driver.Clip = maxBounds;
}
// BUGBUG: v2 - TextFormatter should not change the clip region. If a caller wants to break out of the clip region it should do
// so explicitly.
//var savedClip = Application.Driver?.Clip;
//if (Application.Driver != null) {
// Application.Driver.Clip = maxBounds;
//}
var lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0;
for (int line = lineOffset; line < linesFormated.Count; line++) {
@@ -1326,9 +1329,9 @@ namespace Terminal.Gui {
}
}
}
if (Application.Driver != null) {
Application.Driver.Clip = (Rect)savedClip;
}
//if (Application.Driver != null) {
// Application.Driver.Clip = (Rect)savedClip;
//}
}
}
}

View File

@@ -121,6 +121,9 @@ namespace Terminal.Gui {
rightChar = 'R';
topChar = 'T';
bottomChar = 'B';
if (!string.IsNullOrEmpty (label)) {
leftChar = rightChar = bottomChar = topChar = label [0];
}
}
ustring hrule = ustring.Empty;

View File

@@ -737,47 +737,47 @@ namespace Terminal.Gui {
}
///<inheritdoc/>
public override void Redraw (Rect bounds)
{
if (!Visible) {
return;
}
//public override void Redraw (Rect bounds)
//{
// if (!Visible) {
// return;
// }
if (!_needsDisplay.IsEmpty || _childNeedsDisplay || LayoutNeeded) {
Driver.SetAttribute (GetNormalColor ());
// if (!_needsDisplay.IsEmpty || _childNeedsDisplay || LayoutNeeded) {
// Driver.SetAttribute (GetNormalColor ());
// This is the Application.Top. Clear just the region we're being asked to redraw
// (the bounds passed to us).
Clear ();
Driver.SetAttribute (Enabled ? Colors.Base.Normal : Colors.Base.Disabled);
// // This is the Application.Top. Clear just the region we're being asked to redraw
// // (the bounds passed to us).
// Clear ();
// Driver.SetAttribute (Enabled ? Colors.Base.Normal : Colors.Base.Disabled);
LayoutSubviews ();
PositionToplevels ();
// LayoutSubviews ();
// PositionToplevels ();
if (this == Application.MdiTop) {
foreach (var top in Application.MdiChildes.AsEnumerable ().Reverse ()) {
if (top.Frame.IntersectsWith (bounds)) {
if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) {
top.SetNeedsLayout ();
top.SetNeedsDisplay (top.Bounds);
top.Redraw (top.Bounds);
}
}
}
}
// if (this == Application.MdiTop) {
// foreach (var top in Application.MdiChildes.AsEnumerable ().Reverse ()) {
// if (top.Frame.IntersectsWith (bounds)) {
// if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) {
// top.SetNeedsLayout ();
// top.SetNeedsDisplay (top.Bounds);
// top.Redraw (top.Bounds);
// }
// }
// }
// }
foreach (var view in Subviews) {
if (view.Frame.IntersectsWith (bounds) && !OutsideTopFrame (this)) {
view.SetNeedsLayout ();
view.SetNeedsDisplay (view.Bounds);
}
}
// foreach (var view in Subviews) {
// if (view.Frame.IntersectsWith (bounds) && !OutsideTopFrame (this)) {
// view.SetNeedsLayout ();
// view.SetNeedsDisplay (view.Bounds);
// }
// }
// BUGBUG: shouldn't we just return here? the call to base.Redraw below is redundant
}
// // BUGBUG: shouldn't we just return here? the call to base.Redraw below is redundant
// }
base.Redraw (Bounds);
}
// base.Redraw (Bounds);
//}
bool OutsideTopFrame (Toplevel top)
{

View File

@@ -578,19 +578,15 @@ namespace Terminal.Gui {
}
/// <summary>
/// The bounds represent the View-relative rectangle used for this view; the area inside of the view.
/// The View-relative rectangle where View content is displayed. SubViews are positioned relative to
/// Bounds.<see cref="Rect.Location">Location</see> (which is always (0, 0)) and <see cref="Redraw(Rect)"/> clips drawing to
/// Bounds.<see cref="Rect.Size">Size</see>.
/// </summary>
/// <value>The bounds.</value>
/// <remarks>
/// <para>
/// Updates to the Bounds update the <see cref="Frame"/>,
/// and has the same side effects as updating the <see cref="Frame"/>.
/// </para>
/// <para>
/// Because <see cref="Bounds"/> coordinates are relative to the upper-left corner of the <see cref="View"/>,
/// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0).
/// Use this property to obtain the size and coordinates of the client area of the
/// control for tasks such as drawing on the surface of the control.
/// The <see cref="Rect.Location"/> of Bounds is always (0, 0).
/// To obtain the Frame-relative location of the content area use <see cref="ContentArea"/>.
/// </para>
/// </remarks>
public virtual Rect Bounds {
@@ -600,7 +596,7 @@ namespace Terminal.Gui {
return new Rect (default, Frame.Size);
}
var frameRelativeBounds = Padding.Thickness.GetInnerRect (Padding.Frame);
return frameRelativeBounds;
return new Rect (default, frameRelativeBounds.Size);
}
set {
throw new InvalidOperationException ("It makes no sense to explicitly set Bounds.");
@@ -1292,8 +1288,8 @@ namespace Terminal.Gui {
while (super != null) {
if (!(super.Padding == null || super.BorderFrame == null || super.Margin == null)) {
var inner = super.Padding.Thickness.GetInnerRect (super.BorderFrame.Thickness.GetInnerRect (super.Margin.Thickness.GetInnerRect (super.Frame)));
rrow += super.Frame.Y;
rcol += super.Frame.X;
rrow += inner.Y;
rcol += inner.X;
} else {
rrow += super.Frame.Y;
rcol += super.Frame.X;
@@ -1342,9 +1338,13 @@ namespace Terminal.Gui {
/// </remarks>
public Rect ClipToBounds ()
{
return SetClip (Bounds);
var clip = Bounds ;
return SetClip (clip);
}
// BUGBUG: v2 - SetClip should return VIEW-relative so that it can be used to reset it; using Driver.Clip directly should not be necessary.
/// <summary>
/// Sets the clip region to the specified view-relative region.
/// </summary>
@@ -1360,13 +1360,13 @@ namespace Terminal.Gui {
return previous;
}
// TODO: v2 - Deprecate this API - callers should use LineCanvas instead
/// <summary>
/// Draws a frame in the current view, clipped by the boundary of this view
/// </summary>
/// <param name="region">View-relative region for the frame to be drawn.</param>
/// <param name="padding">The padding to add around the outside of the drawn frame.</param>
/// <param name="fill">If set to <see langword="true"/> it fill will the contents.</param>
[ObsoleteAttribute("This method is obsolete in v2. Use use LineCanvas or Frame instead instead.", false)]
public void DrawFrame (Rect region, int padding = 0, bool fill = false)
{
var scrRect = ViewToScreen (region);
@@ -1632,10 +1632,17 @@ namespace Terminal.Gui {
/// <returns></returns>
public virtual bool OnDrawFrames (Rect bounds)
{
var prevClip = Driver.Clip;
if (SuperView != null) {
Driver.Clip = SuperView.ClipToBounds ();
}
Margin?.Redraw (Margin.Frame);
BorderFrame?.Redraw (BorderFrame.Frame);
Padding?.Redraw (Padding.Frame);
Driver.Clip = prevClip;
//var margin = Margin.Thickness.GetInnerRect (frame);
//var padding = BorderFrame.Thickness.GetInnerRect (margin);
//var content = Padding.Thickness.GetInnerRect (padding);
@@ -1675,32 +1682,20 @@ namespace Terminal.Gui {
return;
}
var prevClip = Driver.Clip;
Driver.Clip = ViewToScreen (Bounds);
if (ColorScheme != null) {
Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
}
OnDrawFrames (Frame);
var prevClip = ClipToBounds ();
// TODO: Implement complete event
// OnDrawFramesComplete (Frame)
//if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
// Rect containerBounds = GetContainerBounds ();
// if (!containerBounds.IsEmpty) {
// Clear (GetNeedDisplay (containerBounds));
// SetChildNeedsDisplay ();
// // Draw any Text
// if (TextFormatter != null) {
// TextFormatter.NeedsFormat = true;
// }
// TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
// HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
// containerBounds);
// }
//}
if (ColorScheme != null) {
//Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
Driver.SetAttribute (GetNormalColor ());
}
Clear (ViewToScreen(bounds));
// Invoke DrawContentEvent
OnDrawContent (bounds);
@@ -1709,20 +1704,17 @@ namespace Terminal.Gui {
// TODO: Implement OnDrawSubviews (cancelable);
if (subviews != null) {
foreach (var view in subviews) {
if (!view._needsDisplay.IsEmpty || view._childNeedsDisplay || view.LayoutNeeded) {
if (view.Frame.IntersectsWith (bounds)) { // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
if (true) { //!view._needsDisplay.IsEmpty || view._childNeedsDisplay || view.LayoutNeeded) {
if (true) { //view.Frame.IntersectsWith (bounds)) { // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
if (view.LayoutNeeded) {
view.LayoutSubviews ();
}
// Draw the subview
// Use the view's bounds (view-relative; Location will always be (0,0)
if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
var rect = view.Bounds;
view.OnDrawContent (rect);
view.Redraw (rect);
view.OnDrawContentComplete (rect);
}
//if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
view.Redraw (view.Bounds);
//}
}
view.ClearNeedsDisplay ();
}
@@ -1732,35 +1724,36 @@ namespace Terminal.Gui {
// Invoke DrawContentCompleteEvent
OnDrawContentComplete (bounds);
// BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
Driver.Clip = prevClip;
ClearLayoutNeeded ();
ClearNeedsDisplay ();
}
// Gets the screen relative rectangle describing the larger of our Superview's bounds or the Driver.Cliprect.
internal Rect GetContainerBounds ()
{
// Get the screen-relative rectangle describing our superview's Bounds
var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
//// Gets the screen relative rectangle describing the larger of our Superview's bounds or the Driver.Cliprect.
//internal Rect GetContainerBounds ()
//{
// // Get the screen-relative rectangle describing our superview's Bounds
// var containerBounds = SuperView == null ? default : SuperView.ViewToScreen (SuperView.Bounds);
// Ensure if clip is larger, we grow
var driverClip = Driver == null ? Rect.Empty : Driver.Clip;
containerBounds.X = Math.Max (containerBounds.X, driverClip.X);
containerBounds.Y = Math.Max (containerBounds.Y, driverClip.Y);
var lenOffset = (driverClip.X + driverClip.Width) - (containerBounds.X + containerBounds.Width);
if (containerBounds.X + containerBounds.Width > driverClip.X + driverClip.Width) {
containerBounds.Width = Math.Max (containerBounds.Width + lenOffset, 0);
} else {
containerBounds.Width = Math.Min (containerBounds.Width, driverClip.Width);
}
lenOffset = (driverClip.Y + driverClip.Height) - (containerBounds.Y + containerBounds.Height);
if (containerBounds.Y + containerBounds.Height > driverClip.Y + driverClip.Height) {
containerBounds.Height = Math.Max (containerBounds.Height + lenOffset, 0);
} else {
containerBounds.Height = Math.Min (containerBounds.Height, driverClip.Height);
}
return containerBounds;
}
// // Ensure if clip is larger, we grow
// var driverClip = Driver == null ? Rect.Empty : Driver.Clip;
// containerBounds.X = Math.Max (containerBounds.X, driverClip.X);
// containerBounds.Y = Math.Max (containerBounds.Y, driverClip.Y);
// var lenOffset = (driverClip.X + driverClip.Width) - (containerBounds.X + containerBounds.Width);
// if (containerBounds.X + containerBounds.Width > driverClip.X + driverClip.Width) {
// containerBounds.Width = Math.Max (containerBounds.Width + lenOffset, 0);
// } else {
// containerBounds.Width = Math.Min (containerBounds.Width, driverClip.Width);
// }
// lenOffset = (driverClip.Y + driverClip.Height) - (containerBounds.Y + containerBounds.Height);
// if (containerBounds.Y + containerBounds.Height > driverClip.Y + driverClip.Height) {
// containerBounds.Height = Math.Max (containerBounds.Height + lenOffset, 0);
// } else {
// containerBounds.Height = Math.Min (containerBounds.Height, driverClip.Height);
// }
// return containerBounds;
//}
/// <summary>
/// Event invoked when the content area of the View is to be drawn.
@@ -1789,33 +1782,14 @@ namespace Terminal.Gui {
DrawContent?.Invoke (contentArea);
if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
Rect containerBounds = GetContainerBounds ();
if (!containerBounds.IsEmpty) {
Clear (GetNeedsDisplayRectScreen (containerBounds));
SetSubViewNeedsDisplay ();
// Draw any Text
if (TextFormatter != null) {
TextFormatter.NeedsFormat = true;
}
TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
containerBounds);
if (TextFormatter != null) {
TextFormatter.NeedsFormat = true;
}
TextFormatter?.Draw (ViewToScreen (contentArea), HasFocus ? GetFocusColor () : GetNormalColor (),
HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
new Rect (ViewToScreen (contentArea).Location, Bounds.Size), true);
SetSubViewNeedsDisplay ();
}
//if (!ustring.IsNullOrEmpty (TextFormatter.Text)) {
// //Rect containerBounds = GetContainerBounds ();
// //Clear (ViewToScreen (GetNeedDisplay (containerBounds)));
// SetChildNeedsDisplay ();
// // Draw any Text
// if (TextFormatter != null) {
// TextFormatter.NeedsFormat = true;
// }
// TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
// HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
// contentArea, false);
//}
}
/// <summary>
@@ -2926,7 +2900,8 @@ namespace Terminal.Gui {
if (ForceValidatePosDim) {
aSize = SetWidthHeight (nBoundsSize);
} else {
Bounds = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height);
Height = nBoundsSize.Height;
Width = nBoundsSize.Width; // = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height);
}
}
TextFormatter.Size = GetBoundsTextFormatterSize ();

View File

@@ -8,14 +8,16 @@ namespace UICatalog.Scenarios {
{
Application.Init ();
Application.Top.ColorScheme = colorScheme;
}
public override void Setup ()
{
ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
var containerLabel = new Label () {
X = 0,
Y = 0,
Width = Dim.Fill(),
Width = Dim.Fill (),
};
Application.Top.Add (containerLabel);
@@ -24,12 +26,12 @@ namespace UICatalog.Scenarios {
Y = 3,
Height = Dim.Fill (2),
Width = Dim.Fill (2),
Title = "View"
Title = "View with 2xMargin, 2xBorder, & 2xPadding",
ColorScheme = Colors.ColorSchemes ["Base"],
};
Application.Top.Add (view);
view.InitializeFrames ();
view.Margin.Thickness = new Thickness (2);
view.Margin.Thickness = new Thickness (2, 2, 2, 2);
view.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
view.Margin.Data = "Margin";
view.BorderFrame.Thickness = new Thickness (2);
@@ -40,14 +42,104 @@ namespace UICatalog.Scenarios {
view.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
view.Padding.Data = "Padding";
Application.Top.Add (view);
containerLabel.LayoutComplete += (a) => {
containerLabel.Text = $"Container.Frame: {Application.Top.Frame} .Bounds: {Application.Top.Bounds}\nView.Frame: {view.Frame} .Bounds: {view.Bounds}\nView.ContentArea: {view.ContentArea}";
var view2 = new View () {
X = 2,
Y = 3,
Height = 7,
Width = 17,
Title = "View2",
Text = "View #2",
TextAlignment = TextAlignment.Centered
};
view2.InitializeFrames ();
view2.Margin.Thickness = new Thickness (1);
view2.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
view2.Margin.Data = "Margin";
view2.BorderFrame.Thickness = new Thickness (1);
view2.BorderFrame.BorderStyle = BorderStyle.Single;
view2.BorderFrame.ColorScheme = view.ColorScheme;
view2.BorderFrame.Data = "BorderFrame";
view2.Padding.Thickness = new Thickness (1);
view2.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
view2.Padding.Data = "Padding";
view.Add (view2);
var view3 = new View () {
X = Pos.Right (view2) + 1,
Y = 3,
Height = 5,
Width = 37,
Title = "View3",
Text = "View #3 (Right(view2)+1",
TextAlignment = TextAlignment.Centered
};
view3.InitializeFrames ();
view3.Margin.Thickness = new Thickness (1, 1, 0, 0);
view3.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
view3.Margin.Data = "Margin";
view3.BorderFrame.Thickness = new Thickness (1, 1, 1, 1);
view3.BorderFrame.BorderStyle = BorderStyle.Single;
view3.BorderFrame.ColorScheme = view.ColorScheme;
view3.BorderFrame.Data = "BorderFrame";
view3.Padding.Thickness = new Thickness (1, 1, 0, 0);
view3.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
view3.Padding.Data = "Padding";
view.Add (view3);
var view4 = new View () {
X = Pos.Right (view3) + 1,
Y = 3,
Height = 5,
Width = 37,
Title = "View4",
Text = "View #4 (Right(view3)+1",
TextAlignment = TextAlignment.Centered
};
view4.InitializeFrames ();
view4.Margin.Thickness = new Thickness (0, 0, 1, 1);
view4.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
view4.Margin.Data = "Margin";
view4.BorderFrame.Thickness = new Thickness (1, 1, 1, 1);
view4.BorderFrame.BorderStyle = BorderStyle.Single;
view4.BorderFrame.ColorScheme = view.ColorScheme;
view4.BorderFrame.Data = "BorderFrame";
view4.Padding.Thickness = new Thickness (0, 0, 1, 1);
view4.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
view4.Padding.Data = "Padding";
view.Add (view4);
var view5 = new View () {
X = Pos.Right (view4) + 1,
Y = 3,
Height = Dim.Fill(2),
Width = Dim.Fill(),
Title = "View5",
Text = "View #5 (Right(view4)+1 Fill",
TextAlignment = TextAlignment.Centered
};
view5.InitializeFrames ();
view5.Margin.Thickness = new Thickness (0, 0, 0, 0);
view5.Margin.ColorScheme = Colors.ColorSchemes ["Toplevel"];
view5.Margin.Data = "Margin";
view5.BorderFrame.Thickness = new Thickness (1, 1, 1, 1);
view5.BorderFrame.BorderStyle = BorderStyle.Single;
view5.BorderFrame.ColorScheme = view.ColorScheme;
view5.BorderFrame.Data = "BorderFrame";
view5.Padding.Thickness = new Thickness (0, 0, 0, 0);
view5.Padding.ColorScheme = Colors.ColorSchemes ["Error"];
view5.Padding.Data = "Padding";
view.Add (view5);
var label = new Label () {
ColorScheme = Colors.ColorSchemes ["Error"],
Text = "AutoSize true; 1;1:",
AutoSize = true,
X = 1,
@@ -69,15 +161,15 @@ namespace UICatalog.Scenarios {
Text = "Right (edit) + 1",
X = Pos.Right (edit) + 1,
Y = 1,
Width = 20 ,
Width = 20,
Height = 1
};
view.Add (edit);
edit = new TextField () {
Text = "Center();50%",
X = Pos.Center(),
Y = Pos.Percent(50),
X = Pos.Center (),
Y = Pos.Percent (50),
Width = 30,
Height = 1
};
@@ -112,12 +204,18 @@ namespace UICatalog.Scenarios {
edit = new TextField () {
Text = "Left;AnchorEnd (2)",
Y = 1 + Pos.Center(),
X = 0,
Y = Pos.AnchorEnd (2),
Width = 30,
Height = 1
};
edit.X = 0;
view.Add (edit);
containerLabel.LayoutComplete += (a) => {
containerLabel.Text = $"Container.Frame: {Application.Top.Frame} .Bounds: {Application.Top.Bounds}\nView.Frame: {view.Frame} .Bounds: {view.Bounds}\nView.ContentArea: {view.ContentArea}";
};
}
}
}