1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-22 20:58:34 +01:00

tabbing, part 1!

This commit is contained in:
Ellpeck 2019-08-28 18:27:17 +02:00
parent cc20682d47
commit 5c741a98e9
15 changed files with 173 additions and 71 deletions

View file

@ -86,7 +86,8 @@ namespace Demos {
// (like above), these custom values don't get undone // (like above), these custom values don't get undone
HasCustomStyle = true, HasCustomStyle = true,
Texture = this.testPatch, Texture = this.testPatch,
HoveredColor = Color.LightGray HoveredColor = Color.LightGray,
SelectionIndicator = style.SelectionIndicator
}); });
root.AddChild(new VerticalSpace(3)); root.AddChild(new VerticalSpace(3));
@ -129,7 +130,7 @@ namespace Demos {
root.AddChild(new RadioButton(Anchor.AutoLeft, new Vector2(1, 10), "Radio button 2!") {PositionOffset = new Vector2(0, 1)}); root.AddChild(new RadioButton(Anchor.AutoLeft, new Vector2(1, 10), "Radio button 2!") {PositionOffset = new Vector2(0, 1)});
var tooltip = new Tooltip(50, "This is a test tooltip to see the window bounding") {IsHidden = true}; var tooltip = new Tooltip(50, "This is a test tooltip to see the window bounding") {IsHidden = true};
this.UiSystem.Add("TestTooltip", tooltip); this.UiSystem.Add("TestTooltip", tooltip).CanSelectContent = false;
root.AddChild(new VerticalSpace(3)); root.AddChild(new VerticalSpace(3));
root.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), "Toggle Test Tooltip") { root.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), "Toggle Test Tooltip") {
OnPressed = element => tooltip.IsHidden = !tooltip.IsHidden OnPressed = element => tooltip.IsHidden = !tooltip.IsHidden

View file

@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using MLEM.Extensions; using MLEM.Extensions;
using MLEM.Input; using MLEM.Input;
using MLEM.Textures;
using MLEM.Ui.Style; using MLEM.Ui.Style;
namespace MLEM.Ui.Elements { namespace MLEM.Ui.Elements {
@ -98,11 +99,11 @@ namespace MLEM.Ui.Elements {
this.InitStyle(this.system.Style); this.InitStyle(this.system.Style);
} }
} }
protected InputHandler Input => this.System.InputHandler;
protected UiControls Controls => this.System.Controls; protected UiControls Controls => this.System.Controls;
protected InputHandler Input => this.Controls.Input;
public Point MousePos => this.Input.MousePosition;
public RootElement Root { get; private set; } public RootElement Root { get; private set; }
public float Scale => this.Root.ActualScale; public float Scale => this.Root.ActualScale;
public Point MousePos => this.Input.MousePosition;
public Element Parent { get; private set; } public Element Parent { get; private set; }
public bool IsMouseOver { get; private set; } public bool IsMouseOver { get; private set; }
public bool IsSelected { get; private set; } public bool IsSelected { get; private set; }
@ -116,7 +117,8 @@ namespace MLEM.Ui.Elements {
this.SetAreaDirty(); this.SetAreaDirty();
} }
} }
public bool IgnoresMouse; public bool CanBeSelected = true;
public bool CanBeMoused = true;
public float DrawAlpha = 1; public float DrawAlpha = 1;
public bool HasCustomStyle; public bool HasCustomStyle;
public bool SetHeightBasedOnChildren; public bool SetHeightBasedOnChildren;
@ -153,6 +155,7 @@ namespace MLEM.Ui.Elements {
} }
private bool areaDirty; private bool areaDirty;
private bool sortedChildrenDirty; private bool sortedChildrenDirty;
public NinePatch SelectionIndicator;
public Element(Anchor anchor, Vector2 size) { public Element(Anchor anchor, Vector2 size) {
this.anchor = anchor; this.anchor = anchor;
@ -422,6 +425,10 @@ namespace MLEM.Ui.Elements {
if (!child.IsHidden) if (!child.IsHidden)
child.Draw(time, batch, alpha * child.DrawAlpha, offset); child.Draw(time, batch, alpha * child.DrawAlpha, offset);
} }
if (this.IsSelected && this.Controls.ShowSelectionIndicator && this.SelectionIndicator != null) {
batch.Draw(this.SelectionIndicator, this.DisplayArea.OffsetCopy(offset), Color.White * alpha);
}
} }
public virtual void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState = null, SamplerState samplerState = null) { public virtual void DrawEarly(GameTime time, SpriteBatch batch, float alpha, BlendState blendState = null, SamplerState samplerState = null) {
@ -432,17 +439,18 @@ namespace MLEM.Ui.Elements {
} }
public virtual Element GetMousedElement() { public virtual Element GetMousedElement() {
if (this.IsHidden || this.IgnoresMouse) if (this.IsHidden)
return null; return null;
for (var i = this.SortedChildren.Count - 1; i >= 0; i--) { for (var i = this.SortedChildren.Count - 1; i >= 0; i--) {
var element = this.SortedChildren[i].GetMousedElement(); var element = this.SortedChildren[i].GetMousedElement();
if (element != null) if (element != null)
return element; return element;
} }
return this.Area.Contains(this.MousePos) ? this : null; return this.CanBeMoused && this.Area.Contains(this.MousePos) ? this : null;
} }
protected virtual void InitStyle(UiStyle style) { protected virtual void InitStyle(UiStyle style) {
this.SelectionIndicator = style.SelectionIndicator;
} }
public delegate void TextInputCallback(Element element, Keys key, char character); public delegate void TextInputCallback(Element element, Keys key, char character);

View file

@ -7,10 +7,7 @@ namespace MLEM.Ui.Elements {
public static Button ImageButton(Anchor anchor, Vector2 size, TextureRegion texture, string text = null, string tooltipText = null, float tooltipWidth = 50, int imagePadding = 2) { public static Button ImageButton(Anchor anchor, Vector2 size, TextureRegion texture, string text = null, string tooltipText = null, float tooltipWidth = 50, int imagePadding = 2) {
var button = new Button(anchor, size, text, tooltipText, tooltipWidth); var button = new Button(anchor, size, text, tooltipText, tooltipWidth);
var image = new Image(Anchor.CenterLeft, Vector2.One, texture) { var image = new Image(Anchor.CenterLeft, Vector2.One, texture) {Padding = new Point(imagePadding)};
Padding = new Point(imagePadding),
IgnoresMouse = true
};
button.OnAreaUpdated += e => image.Size = new Vector2(e.Area.Height, e.Area.Height) / e.Scale; button.OnAreaUpdated += e => image.Size = new Vector2(e.Area.Height, e.Area.Height) / e.Scale;
button.AddChild(image, 0); button.AddChild(image, 0);
return button; return button;

View file

@ -5,6 +5,7 @@ namespace MLEM.Ui.Elements {
public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : base(anchor, size) { public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : base(anchor, size) {
this.SetHeightBasedOnChildren = setHeightBasedOnChildren; this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
this.CanBeSelected = false;
} }
} }

View file

@ -15,6 +15,7 @@ namespace MLEM.Ui.Elements {
public Image(Anchor anchor, Vector2 size, TextureRegion texture, bool scaleToImage = false) : base(anchor, size) { public Image(Anchor anchor, Vector2 size, TextureRegion texture, bool scaleToImage = false) : base(anchor, size) {
this.Texture = texture; this.Texture = texture;
this.ScaleToImage = scaleToImage; this.ScaleToImage = scaleToImage;
this.CanBeSelected = false;
} }
protected override Point CalcActualSize(Rectangle parentArea) { protected override Point CalcActualSize(Rectangle parentArea) {

View file

@ -19,6 +19,7 @@ namespace MLEM.Ui.Elements {
this.SetHeightBasedOnChildren = setHeightBasedOnChildren; this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
this.scrollOverflow = scrollOverflow; this.scrollOverflow = scrollOverflow;
this.ChildPadding = new Point(5); this.ChildPadding = new Point(5);
this.CanBeSelected = false;
if (scrollOverflow) { if (scrollOverflow) {
var scrollSize = scrollerSize ?? Point.Zero; var scrollSize = scrollerSize ?? Point.Zero;

View file

@ -45,7 +45,8 @@ namespace MLEM.Ui.Elements {
public Paragraph(Anchor anchor, float width, string text, bool centerText = false) : base(anchor, new Vector2(width, 0)) { public Paragraph(Anchor anchor, float width, string text, bool centerText = false) : base(anchor, new Vector2(width, 0)) {
this.text = text; this.text = text;
this.AutoAdjustWidth = centerText; this.AutoAdjustWidth = centerText;
this.IgnoresMouse = true; this.CanBeSelected = false;
this.CanBeMoused = false;
} }
protected override Point CalcActualSize(Rectangle parentArea) { protected override Point CalcActualSize(Rectangle parentArea) {

View file

@ -43,14 +43,15 @@ namespace MLEM.Ui.Elements {
this.maxValue = maxValue; this.maxValue = maxValue;
this.Horizontal = horizontal; this.Horizontal = horizontal;
this.ScrollerSize = new Point(horizontal ? scrollerSize : size.X.Floor(), !horizontal ? scrollerSize : size.Y.Floor()); this.ScrollerSize = new Point(horizontal ? scrollerSize : size.X.Floor(), !horizontal ? scrollerSize : size.Y.Floor());
this.CanBeSelected = false;
} }
public override void Update(GameTime time) { public override void Update(GameTime time) {
base.Update(time); base.Update(time);
var moused = this.System.MousedElement; var moused = this.Controls.MousedElement;
if (moused == this && this.Controls.MainButton(this.Input)) { if (moused == this && this.Controls.Input.IsMouseButtonDown(MouseButton.Left)) {
this.isMouseHeld = true; this.isMouseHeld = true;
} else if (this.isMouseHeld && this.Controls.MainButton(this.Input)) { } else if (this.isMouseHeld && !this.Controls.Input.IsMouseButtonDown(MouseButton.Left)) {
this.isMouseHeld = false; this.isMouseHeld = false;
} }
@ -65,7 +66,7 @@ namespace MLEM.Ui.Elements {
} }
if (!this.Horizontal && moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) { if (!this.Horizontal && moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
var scroll = this.Controls.Scroll(this.Input); var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
if (scroll != 0) if (scroll != 0)
this.CurrentValue += this.StepPerScroll * Math.Sign(scroll); this.CurrentValue += this.StepPerScroll * Math.Sign(scroll);
} }

View file

@ -13,9 +13,10 @@ namespace MLEM.Ui.Elements {
base(Anchor.TopLeft, width, text) { base(Anchor.TopLeft, width, text) {
this.AutoAdjustWidth = true; this.AutoAdjustWidth = true;
this.Padding = new Point(2); this.Padding = new Point(2);
this.CanBeSelected = false;
if (elementToHover != null) { if (elementToHover != null) {
elementToHover.OnMouseEnter += element => element.System.Add(element.GetType().Name + "Tooltip", this); elementToHover.OnMouseEnter += element => element.System.Add(element.GetType().Name + "Tooltip", this).CanSelectContent = false;
elementToHover.OnMouseExit += element => element.System.Remove(element.GetType().Name + "Tooltip"); elementToHover.OnMouseExit += element => element.System.Remove(element.GetType().Name + "Tooltip");
} }
} }

View file

@ -4,6 +4,7 @@ namespace MLEM.Ui.Elements {
public class VerticalSpace : Element { public class VerticalSpace : Element {
public VerticalSpace(int height) : base(Anchor.AutoCenter, new Vector2(1, height)) { public VerticalSpace(int height) : base(Anchor.AutoCenter, new Vector2(1, height)) {
this.CanBeSelected = false;
} }
} }

View file

@ -5,6 +5,7 @@ using MLEM.Textures;
namespace MLEM.Ui.Style { namespace MLEM.Ui.Style {
public class UiStyle { public class UiStyle {
public NinePatch SelectionIndicator;
public NinePatch ButtonTexture; public NinePatch ButtonTexture;
public NinePatch ButtonHoveredTexture; public NinePatch ButtonHoveredTexture;
public Color ButtonHoveredColor; public Color ButtonHoveredColor;
@ -28,5 +29,6 @@ namespace MLEM.Ui.Style {
public IGenericFont BoldFont; public IGenericFont BoldFont;
public IGenericFont ItalicFont; public IGenericFont ItalicFont;
public float TextScale = 1; public float TextScale = 1;
} }
} }

View file

@ -9,6 +9,7 @@ namespace MLEM.Ui.Style {
public class UntexturedStyle : UiStyle { public class UntexturedStyle : UiStyle {
public UntexturedStyle(SpriteBatch batch) { public UntexturedStyle(SpriteBatch batch) {
this.SelectionIndicator = GenerateTexture(batch, Color.Transparent, Color.Red);
this.ButtonTexture = GenerateTexture(batch, Color.CadetBlue); this.ButtonTexture = GenerateTexture(batch, Color.CadetBlue);
this.ButtonHoveredColor = Color.LightGray; this.ButtonHoveredColor = Color.LightGray;
this.PanelTexture = GenerateTexture(batch, Color.Gray); this.PanelTexture = GenerateTexture(batch, Color.Gray);
@ -27,12 +28,13 @@ namespace MLEM.Ui.Style {
this.Font = new EmptyFont(); this.Font = new EmptyFont();
} }
private static NinePatch GenerateTexture(SpriteBatch batch, Color color) { private static NinePatch GenerateTexture(SpriteBatch batch, Color color, Color? outlineColor = null) {
var outli = outlineColor ?? Color.Black;
var tex = new Texture2D(batch.GraphicsDevice, 3, 3); var tex = new Texture2D(batch.GraphicsDevice, 3, 3);
tex.SetData(new[] { tex.SetData(new[] {
Color.Black, Color.Black, Color.Black, outli, outli, outli,
Color.Black, color, Color.Black, outli, color, outli,
Color.Black, Color.Black, Color.Black outli, outli, outli
}); });
batch.Disposing += (sender, args) => { batch.Disposing += (sender, args) => {
if (tex != null) { if (tex != null) {

View file

@ -1,15 +1,116 @@
using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MLEM.Input; using MLEM.Input;
using MLEM.Ui.Elements;
namespace MLEM.Ui { namespace MLEM.Ui {
public class UiControls { public class UiControls {
public BoolQuery MainButton = h => h.IsMouseButtonPressed(MouseButton.Left); public readonly InputHandler Input;
public BoolQuery SecondaryButton = h => h.IsMouseButtonPressed(MouseButton.Right); private readonly bool isInputOurs;
public FloatQuery Scroll = h => h.LastScrollWheel - h.ScrollWheel; private readonly UiSystem system;
public delegate bool BoolQuery(InputHandler handler); public Element MousedElement { get; private set; }
public Element SelectedElement { get; private set; }
public bool ShowSelectionIndicator;
public delegate float FloatQuery(InputHandler handler); public UiControls(UiSystem system, InputHandler inputHandler = null) {
this.system = system;
this.Input = inputHandler ?? new InputHandler();
this.isInputOurs = inputHandler == null;
}
public void Update() {
if (this.isInputOurs)
this.Input.Update();
var mousedNow = this.GetMousedElement();
// mouse new element
if (mousedNow != this.MousedElement) {
if (this.MousedElement != null)
this.MousedElement.OnMouseExit?.Invoke(this.MousedElement);
if (mousedNow != null)
mousedNow.OnMouseEnter?.Invoke(mousedNow);
this.MousedElement = mousedNow;
}
if (this.Input.IsMouseButtonPressed(MouseButton.Left)) {
// select element
var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null;
if (this.SelectedElement != selectedNow)
this.SelectElement(selectedNow, false);
// first action on element
if (mousedNow != null)
mousedNow.OnPressed?.Invoke(mousedNow);
} else if (this.Input.IsMouseButtonPressed(MouseButton.Right)) {
// secondary action on element
if (mousedNow != null)
mousedNow.OnSecondaryPressed?.Invoke(mousedNow);
} else if (this.Input.IsKeyPressed(Keys.Enter) || this.Input.IsKeyPressed(Keys.Space)) {
if (this.SelectedElement != null) {
if (this.Input.IsModifierKeyDown(ModifierKey.Shift)) {
// secondary action on element using space or enter
this.SelectedElement.OnSecondaryPressed?.Invoke(this.SelectedElement);
} else {
// first action on element using space or enter
this.SelectedElement.OnPressed?.Invoke(this.SelectedElement);
}
}
} else if (this.Input.IsKeyPressed(Keys.Tab)) {
// tab or shift-tab to next or previous element
this.SelectElement(this.GetNextElement(this.Input.IsModifierKeyDown(ModifierKey.Shift)), true);
}
}
public void SelectElement(Element element, bool show) {
if (this.SelectedElement != null)
this.SelectedElement.OnDeselected?.Invoke(this.SelectedElement);
if (element != null)
element.OnSelected?.Invoke(element);
this.SelectedElement = element;
this.ShowSelectionIndicator = show;
}
public Element GetMousedElement() {
foreach (var root in this.system.GetRootElements()) {
var moused = root.Element.GetMousedElement();
if (moused != null)
return moused;
}
return null;
}
private Element GetNextElement(bool backward) {
var currRoot = this.system.GetRootElements().FirstOrDefault(root => root.CanSelectContent);
if (currRoot == null)
return null;
var children = currRoot.Element.GetChildren(regardChildrensChildren: true);
if (this.SelectedElement == null || this.SelectedElement.Root != currRoot) {
return backward ? children.LastOrDefault(c => c.CanBeSelected) : children.FirstOrDefault(c => c.CanBeSelected);
} else {
var foundCurr = false;
Element lastFound = null;
foreach (var child in children) {
if (!child.CanBeSelected)
continue;
if (child == this.SelectedElement) {
// when going backwards, return the last element found before the current one
if (backward)
return lastFound;
foundCurr = true;
} else {
// when going forwards, return the element after the current one
if (!backward && foundCurr)
return child;
}
lastFound = child;
}
return null;
}
}
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions; using MLEM.Extensions;
@ -14,8 +15,6 @@ namespace MLEM.Ui {
public readonly GraphicsDevice GraphicsDevice; public readonly GraphicsDevice GraphicsDevice;
public Rectangle Viewport { get; private set; } public Rectangle Viewport { get; private set; }
private readonly List<RootElement> rootElements = new List<RootElement>(); private readonly List<RootElement> rootElements = new List<RootElement>();
public readonly InputHandler InputHandler;
private readonly bool isInputOurs;
public bool AutoScaleWithScreen; public bool AutoScaleWithScreen;
public Point AutoScaleReferenceSize; public Point AutoScaleReferenceSize;
@ -33,8 +32,6 @@ namespace MLEM.Ui {
root.Element.ForceUpdateArea(); root.Element.ForceUpdateArea();
} }
} }
public Element MousedElement { get; private set; }
public Element SelectedElement { get; private set; }
private UiStyle style; private UiStyle style;
public UiStyle Style { public UiStyle Style {
get => this.style; get => this.style;
@ -49,12 +46,11 @@ namespace MLEM.Ui {
public float DrawAlpha = 1; public float DrawAlpha = 1;
public BlendState BlendState; public BlendState BlendState;
public SamplerState SamplerState = SamplerState.PointClamp; public SamplerState SamplerState = SamplerState.PointClamp;
public UiControls Controls = new UiControls(); public UiControls Controls;
public UiSystem(GameWindow window, GraphicsDevice device, UiStyle style, InputHandler inputHandler = null) { public UiSystem(GameWindow window, GraphicsDevice device, UiStyle style, InputHandler inputHandler = null) {
this.Controls = new UiControls(this, inputHandler);
this.GraphicsDevice = device; this.GraphicsDevice = device;
this.InputHandler = inputHandler ?? new InputHandler();
this.isInputOurs = inputHandler == null;
this.style = style; this.style = style;
this.Viewport = device.Viewport.Bounds; this.Viewport = device.Viewport.Bounds;
this.AutoScaleReferenceSize = this.Viewport.Size; this.AutoScaleReferenceSize = this.Viewport.Size;
@ -71,37 +67,7 @@ namespace MLEM.Ui {
} }
public void Update(GameTime time) { public void Update(GameTime time) {
if (this.isInputOurs) this.Controls.Update();
this.InputHandler.Update();
var mousedNow = this.GetMousedElement();
// mouse new element
if (mousedNow != this.MousedElement) {
if (this.MousedElement != null)
this.MousedElement.OnMouseExit?.Invoke(this.MousedElement);
if (mousedNow != null)
mousedNow.OnMouseEnter?.Invoke(mousedNow);
this.MousedElement = mousedNow;
}
if (this.Controls.MainButton(this.InputHandler)) {
// select element
if (this.SelectedElement != mousedNow) {
if (this.SelectedElement != null)
this.SelectedElement.OnDeselected?.Invoke(this.SelectedElement);
if (mousedNow != null)
mousedNow.OnSelected?.Invoke(mousedNow);
this.SelectedElement = mousedNow;
}
// first action on element
if (mousedNow != null)
mousedNow.OnPressed?.Invoke(mousedNow);
} else if (this.Controls.SecondaryButton(this.InputHandler)) {
// secondary action on element
if (mousedNow != null)
mousedNow.OnSecondaryPressed?.Invoke(mousedNow);
}
foreach (var root in this.rootElements) foreach (var root in this.rootElements)
root.Element.Update(time); root.Element.Update(time);
@ -158,13 +124,9 @@ namespace MLEM.Ui {
return this.rootElements.FindIndex(element => element.Name == name); return this.rootElements.FindIndex(element => element.Name == name);
} }
private Element GetMousedElement() { public IEnumerable<RootElement> GetRootElements() {
for (var i = this.rootElements.Count - 1; i >= 0; i--) { for (var i = this.rootElements.Count - 1; i >= 0; i--)
var moused = this.rootElements[i].Element.GetMousedElement(); yield return this.rootElements[i];
if (moused != null)
return moused;
}
return null;
} }
} }
@ -185,6 +147,7 @@ namespace MLEM.Ui {
} }
} }
public float ActualScale => this.System.GlobalScale * this.Scale; public float ActualScale => this.System.GlobalScale * this.Scale;
public bool CanSelectContent = true;
public RootElement(string name, Element element, UiSystem system) { public RootElement(string name, Element element, UiSystem system) {
this.Name = name; this.Name = name;

View file

@ -77,6 +77,19 @@ namespace MLEM.Input {
return this.WasKeyUp(key) && this.IsKeyDown(key); return this.WasKeyUp(key) && this.IsKeyDown(key);
} }
public bool IsModifierKeyDown(ModifierKey modifier) {
switch (modifier) {
case ModifierKey.Shift:
return this.IsKeyDown(Keys.LeftShift) || this.IsKeyDown(Keys.RightShift);
case ModifierKey.Control:
return this.IsKeyDown(Keys.LeftControl) || this.IsKeyDown(Keys.RightControl);
case ModifierKey.Alt:
return this.IsKeyDown(Keys.LeftAlt) || this.IsKeyDown(Keys.RightAlt);
default:
throw new ArgumentException(nameof(modifier));
}
}
public bool IsMouseButtonDown(MouseButton button) { public bool IsMouseButtonDown(MouseButton button) {
return GetState(this.MouseState, button) == ButtonState.Pressed; return GetState(this.MouseState, button) == ButtonState.Pressed;
} }
@ -145,4 +158,12 @@ namespace MLEM.Input {
Extra2 Extra2
} }
public enum ModifierKey {
Shift,
Control,
Alt
}
} }