1 using Microsoft.Xna.Framework;
2 using Microsoft.Xna.Framework.Graphics;
3 using Microsoft.Xna.Framework.Input;
5 using System.Collections.Generic;
25 private GUIFrame backgroundColorPanel;
27 private bool drawGrid, snapToGrid;
33 private readonly List<Sprite> selectedSprites =
new List<Sprite>();
34 private readonly List<Sprite> dirtySprites =
new List<Sprite>();
35 private Texture2D SelectedTexture => lastSprite?.Texture;
37 private string selectedTexturePath;
40 private float zoom = 1;
41 private const float MinZoom = 0.25f, MaxZoom = 10.0f;
50 private bool editBackgroundColor;
51 private Color backgroundColor =
new Color(0.051f, 0.149f, 0.271f, 1.0f);
55 private readonly
Camera cam;
63 get {
return topPanel; }
73 #region Initialization
74 private void CreateUI()
76 originLabel = TextManager.Get(
"charactereditor.origin");
77 positionLabel = TextManager.GetWithVariable(
"charactereditor.position",
"[coordinates]",
string.Empty);
78 sizeLabel = TextManager.Get(
"charactereditor.size");
85 RelativeOffset = new Vector2(0, 0.1f)
86 }, TextManager.Get(
"spriteeditor.reloadtexture"))
88 OnClicked = (button, userData) =>
90 var selected = selectedSprites.ToList();
91 Sprite firstSelected = selected.First();
92 selected.ForEach(s => s.ReloadTexture());
96 texturePathText.
Text = TextManager.GetWithVariable(
"spriteeditor.texturesreloaded",
"[filepath]", firstSelected.
FilePath.
Value);
97 texturePathText.
TextColor = GUIStyle.Green;
101 new GUIButton(
new RectTransform(
new Vector2(0.14f, 0.4f), topPanelContents.
RectTransform,
Anchor.BottomLeft)
103 RelativeOffset = new Vector2(0, 0.1f)
104 }, TextManager.Get(
"spriteeditor.resetchanges"))
106 OnClicked = (button, userData) =>
108 if (SelectedTexture ==
null) {
return false; }
109 foreach (Sprite sprite
in loadedSprites)
111 if (sprite.FullPath != selectedTexturePath) {
continue; }
112 var element = sprite.SourceElement;
113 if (element ==
null) {
continue; }
115 sprite.SourceRect = element.GetAttributeRect(
"sourcerect", sprite.SourceRect);
116 sprite.RelativeOrigin = element.GetAttributeVector2(
"origin",
new Vector2(0.5f, 0.5f));
119 xmlPathText.
Text = TextManager.Get(
"spriteeditor.resetsuccessful");
124 new GUIButton(
new RectTransform(
new Vector2(0.14f, 0.4f), topPanelContents.
RectTransform,
Anchor.TopLeft)
126 RelativeOffset = new Vector2(0.15f, 0.1f)
127 }, TextManager.Get(
"spriteeditor.saveselectedsprites"))
129 OnClicked = (button, userData) =>
131 return SaveSprites(selectedSprites);
134 new GUIButton(
new RectTransform(
new Vector2(0.14f, 0.4f), topPanelContents.
RectTransform,
Anchor.BottomLeft)
136 RelativeOffset = new Vector2(0.15f, 0.1f)
137 }, TextManager.Get(
"spriteeditor.saveallsprites"))
139 OnClicked = (button, userData) =>
141 return SaveSprites(loadedSprites);
145 GUITextBlock.AutoScaleAndNormalize(topPanelContents.
Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock));
147 new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 0.2f), topPanelContents.
RectTransform,
Anchor.TopCenter,
Pivot.CenterRight) { RelativeOffset = new Vector2(0, 0.3f) }, TextManager.Get(
"spriteeditor.zoom"));
148 zoomBar =
new GUIScrollBar(
new RectTransform(
new Vector2(0.2f, 0.35f), topPanelContents.
RectTransform,
Anchor.TopCenter,
Pivot.CenterRight)
150 RelativeOffset = new Vector2(0.05f, 0.3f)
151 }, style:
"GUISlider", barSize: 0.1f)
153 BarScroll = GetBarScrollValue(),
155 OnMoved = (scrollBar, value) =>
157 zoom = MathHelper.Lerp(MinZoom, MaxZoom, value);
158 viewAreaOffset = Point.Zero;
162 var resetBtn =
new GUIButton(
new RectTransform(
new Vector2(0.05f, 0.35f), topPanelContents.
RectTransform,
Anchor.TopCenter,
Pivot.CenterLeft) { RelativeOffset = new Vector2(0.055f, 0.3f) }, TextManager.Get(
"spriteeditor.resetzoom"))
164 OnClicked = (box, data) =>
170 resetBtn.TextBlock.AutoScaleHorizontal =
true;
172 new GUITickBox(
new RectTransform(
new Vector2(0.2f, 0.2f), topPanelContents.
RectTransform,
Anchor.BottomCenter,
Pivot.CenterRight) { RelativeOffset = new Vector2(0, 0.3f) }, TextManager.Get(
"spriteeditor.showgrid"))
175 OnSelected = (tickBox) =>
181 new GUITickBox(
new RectTransform(
new Vector2(0.2f, 0.2f), topPanelContents.
RectTransform,
Anchor.BottomCenter,
Pivot.CenterRight) { RelativeOffset = new Vector2(0.17f, 0.3f) }, TextManager.Get(
"spriteeditor.snaptogrid"))
184 OnSelected = (tickBox) =>
191 texturePathText =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 0.4f), topPanelContents.
RectTransform,
Anchor.Center,
Pivot.BottomCenter) { RelativeOffset = new Vector2(0.4f, 0) },
"", Color.LightGray);
192 xmlPathText =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 0.4f), topPanelContents.
RectTransform,
Anchor.Center,
Pivot.TopCenter) { RelativeOffset = new Vector2(0.4f, 0) },
"", Color.LightGray);
195 { MinSize = new Point(150, 0) }, style:
"GUIFrameLeft");
196 var paddedLeftPanel =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.95f), leftPanel.
RectTransform,
Anchor.Center))
197 { RelativeSpacing = 0.01f, Stretch =
true };
199 var filterArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.03f), paddedLeftPanel.RectTransform) { MinSize = new Point(0, 20) }, isHorizontal:
true)
202 UserData =
"filterarea"
204 filterTexturesLabel =
new GUITextBlock(
new RectTransform(Vector2.One, filterArea.RectTransform), TextManager.Get(
"serverlog.filter"), font: GUIStyle.Font, textAlignment: Alignment.CenterLeft) { IgnoreLayoutGroups =
true }; ;
205 filterTexturesBox =
new GUITextBox(
new RectTransform(
new Vector2(0.8f, 1.0f), filterArea.RectTransform), font: GUIStyle.Font, createClearButton:
true);
207 filterTexturesBox.
OnTextChanged += (textBox, text) => { FilterTextures(text);
return true; };
209 textureList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 1.0f), paddedLeftPanel.RectTransform))
211 PlaySoundOnSelect =
true,
212 OnSelected = (listBox, userData) =>
214 var newTexturePath = userData as string;
215 if (selectedTexturePath ==
null || selectedTexturePath != newTexturePath)
217 selectedTexturePath = newTexturePath;
219 spriteList.
Select(loadedSprites.First(s => s.FilePath == selectedTexturePath), autoScroll: GUIListBox.AutoScroll.Disabled);
220 UpdateScrollBar(spriteList);
224 var textBlock = (GUITextBlock)child;
225 var sprite = (Sprite)textBlock.UserData;
226 textBlock.TextColor =
new Color(textBlock.TextColor, sprite.FilePath == selectedTexturePath ? 1.0f : 0.4f);
227 if (sprite.FilePath == selectedTexturePath) { textBlock.Visible =
true; }
229 texturePathText.
TextColor = Color.LightGray;
230 topPanelContents.
Visible =
true;
236 var paddedRightPanel =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f, 0.95f), rightPanel.
RectTransform,
Anchor.Center))
239 RelativeSpacing = 0.01f
242 filterArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.03f), paddedRightPanel.RectTransform) { MinSize = new Point(0, 20) }, isHorizontal:
true)
245 UserData =
"filterarea"
247 filterSpritesLabel =
new GUITextBlock(
new RectTransform(Vector2.One, filterArea.RectTransform), TextManager.Get(
"serverlog.filter"), font: GUIStyle.Font, textAlignment: Alignment.CenterLeft) { IgnoreLayoutGroups =
true };
248 filterSpritesBox =
new GUITextBox(
new RectTransform(
new Vector2(0.8f, 1.0f), filterArea.RectTransform), font: GUIStyle.Font, createClearButton:
true);
250 filterSpritesBox.
OnTextChanged += (textBox, text) => { FilterSprites(text);
return true; };
252 spriteList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 1.0f), paddedRightPanel.RectTransform))
254 PlaySoundOnSelect =
true,
255 OnSelected = (listBox, userData) =>
257 if (userData is Sprite sprite)
267 bottomPanel =
new GUIFrame(
new RectTransform(
new Vector2(0.5f, 0.05f),
Frame.
RectTransform,
Anchor.BottomCenter), style:
null, color: Color.Black * 0.5f);
268 new GUITickBox(
new RectTransform(
new Vector2(0.2f, 0.5f), bottomPanel.
RectTransform,
Anchor.Center), TextManager.Get(
"charactereditor.editbackgroundcolor"))
277 backgroundColorPanel =
new GUIFrame(
new RectTransform(
new Point(400, 80),
Frame.
RectTransform,
Anchor.BottomCenter) { RelativeOffset = new Vector2(0, 0.1f) }, style:
null, color: Color.Black * 0.4f);
278 new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1), backgroundColorPanel.
RectTransform) { MinSize = new Point(80, 26) }, TextManager.Get(
"spriteeditor.backgroundcolor"), textColor: Color.WhiteSmoke);
279 var inputArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.7f, 1), backgroundColorPanel.
RectTransform,
Anchor.TopRight)
281 AbsoluteOffset = new Point(20, 0)
282 }, isHorizontal:
true, childAnchor:
Anchor.CenterRight)
285 RelativeSpacing = 0.01f
287 var fields =
new GUIComponent[4];
288 LocalizedString[] colorComponentLabels = { TextManager.Get(
"spriteeditor.colorcomponentr"), TextManager.Get(
"spriteeditor.colorcomponentg"), TextManager.Get(
"spriteeditor.colorcomponentb") };
289 for (
int i = 2; i >= 0; i--)
291 var element =
new GUIFrame(
new RectTransform(
new Vector2(0.2f, 1), inputArea.RectTransform)
293 MinSize = new Point(40, 0),
294 MaxSize = new Point(100, 50)
295 }, style:
null, color: Color.Black * 0.6f);
296 var colorLabel =
new GUITextBlock(
new RectTransform(
new Vector2(0.3f, 1), element.RectTransform,
Anchor.CenterLeft), colorComponentLabels[i],
297 font: GUIStyle.SmallFont, textAlignment: Alignment.CenterLeft);
298 var numberInput =
new GUINumberInput(
new RectTransform(
new Vector2(0.7f, 1), element.RectTransform,
Anchor.CenterRight),
NumberType.Int)
300 Font = GUIStyle.SmallFont
302 numberInput.MinValueInt = 0;
303 numberInput.MaxValueInt = 255;
304 numberInput.Font = GUIStyle.SmallFont;
308 colorLabel.TextColor = GUIStyle.Red;
309 numberInput.IntValue = backgroundColor.R;
310 numberInput.OnValueChanged += (numInput) => backgroundColor.R = (
byte)(numInput.IntValue);
313 colorLabel.TextColor = GUIStyle.Green;
314 numberInput.IntValue = backgroundColor.G;
315 numberInput.OnValueChanged += (numInput) => backgroundColor.G = (
byte)(numInput.IntValue);
318 colorLabel.TextColor = Color.DeepSkyBlue;
319 numberInput.IntValue = backgroundColor.B;
320 numberInput.OnValueChanged += (numInput) => backgroundColor.B = (
byte)(numInput.IntValue);
326 private readonly HashSet<Sprite> loadedSprites =
new HashSet<Sprite>();
327 private void LoadSprites()
329 loadedSprites.ForEach(s => s.Remove());
330 loadedSprites.Clear();
331 var contentPackages = ContentPackageManager.EnabledPackages.All.ToList();
334 var vanilla = GameMain.VanillaContent;
337 contentPackages.Remove(vanilla);
340 foreach (var contentPackage
in contentPackages)
342 foreach (var file
in contentPackage.Files)
344 if (file.Path.EndsWith(
".xml"))
346 XDocument doc = XMLExtensions.TryLoadXml(file.Path);
349 LoadSprites(doc.Root.FromPackage(file.Path.ContentPackage));
355 void LoadSprites(ContentXElement element)
357 string[] spriteElementNames = {
375 foreach (
string spriteElementName
in spriteElementNames)
377 element.GetChildElements(spriteElementName).ForEach(s => CreateSprite(s));
380 element.Elements().ForEach(e => LoadSprites(e));
383 void CreateSprite(ContentXElement element)
386 if (element.Attributes().None()) {
return; }
388 string spriteFolder =
"";
389 ContentPath texturePath =
null;
391 if (element.GetAttribute(
"texture") !=
null)
393 texturePath = element.GetAttributeContentPath(
"texture");
397 if (element.Name.ToString().ToLower() ==
"vinesprite")
399 texturePath = element.Parent.GetAttributeContentPath(
"vineatlas");
402 if (texturePath.IsNullOrEmpty()) {
return; }
405 if (texturePath.Value.Contains(
"[GENDER]") || texturePath.Value.Contains(
"[HEADID]") || texturePath.Value.Contains(
"[RACE]") || texturePath.Value.Contains(
"[VARIANT]")) {
return; }
406 if (!texturePath.Value.Contains(
"/"))
408 var parsedPath = element.ParseContentPathFromUri();
409 spriteFolder = Path.GetDirectoryName(parsedPath);
417 loadedSprites.Add(
new Sprite(element, spriteFolder, texturePath.Value, lazyLoad:
true));
421 private bool SaveSprites(IEnumerable<Sprite> sprites)
423 if (SelectedTexture ==
null) {
return false; }
424 if (sprites.None()) {
return false; }
425 HashSet<XDocument> docsToSave =
new HashSet<XDocument>();
426 foreach (Sprite sprite
in sprites)
428 if (sprite.FullPath != selectedTexturePath) {
continue; }
429 var element = sprite.SourceElement;
430 if (element ==
null) {
continue; }
431 element.SetAttributeValue(
"sourcerect", XMLExtensions.RectToString(sprite.SourceRect));
432 element.SetAttributeValue(
"origin", XMLExtensions.Vector2ToString(sprite.RelativeOrigin));
443 docsToSave.Add(element.Document);
445 xmlPathText.
Text = TextManager.Get(
"spriteeditor.allchangessavedto");
446 foreach (XDocument doc
in docsToSave)
448 string xmlPath = doc.ParseContentPathFromUri();
449 xmlPathText.
Text +=
"\n" + xmlPath;
453 doc.SaveSafe(xmlPath);
461 #region Public methods
468 if (editBackgroundColor)
474 public override void Update(
double deltaTime)
476 base.Update(deltaTime);
477 Widget.EnableMultiSelect = ControlDown;
480 if (Widget.SelectedWidgets.None() || Widget.EnableMultiSelect)
482 if (SelectedTexture !=
null && GUI.MouseOn ==
null)
484 foreach (
Sprite sprite
in loadedSprites)
486 if (sprite.
FullPath != selectedTexturePath) {
continue; }
493 UpdateScrollBar(spriteList);
494 UpdateScrollBar(textureList);
496 GUI.KeyboardDispatcher.Subscriber =
null;
502 if (GUI.MouseOn ==
null)
507 float zoomDeltaPrc = ((newZoom - zoom) / zoom);
512 Point mouseToViewAreaScreenCenterDelta = (GetViewArea.Center - viewAreaOffset) -
PlayerInput.
MousePosition.ToPoint();
513 Vector2 mouseDelta = mouseToViewAreaScreenCenterDelta.ToVector2();
515 Vector2 newViewAreaOffset = viewAreaOffset.ToVector2();
516 newViewAreaOffset += (mouseDelta + newViewAreaOffset) * zoomDeltaPrc;
517 viewAreaOffset = newViewAreaOffset.ToPoint();
519 widgets.Values.ForEach(w => w.Update((
float)deltaTime));
524 viewAreaOffset += moveSpeed.ToPoint();
527 if (GUI.KeyboardDispatcher.Subscriber ==
null)
532 if (selectedSprites.Count == 1)
534 var selectedSprite = selectedSprites.First();
535 if (selectedSprite.SourceElement !=
null)
537 string sourceRectText = $
"sourcerect=\"{XMLExtensions.RectToString(selectedSprite.SourceRect)}\"";
538 text += $
"{sourceRectText}";
543 foreach (var selectedSprite
in selectedSprites)
545 if (selectedSprite.SourceElement ==
null) {
continue; }
546 XElement xElement =
new XElement(selectedSprite.SourceElement.Element);
547 xElement.SetAttributeValue(
"sourcerect", XMLExtensions.RectToString(selectedSprite.SourceRect));
548 xElement.SetAttributeValue(
"origin", XMLExtensions.Vector2ToString(selectedSprite.RelativeOrigin));
549 text += $
"{xElement}";
550 if (selectedSprites.Last() != selectedSprite)
552 text += Environment.NewLine;
557 Clipboard.SetText(text);
577 holdTimer += deltaTime;
578 if (holdTimer > holdTime)
585 holdTimer += deltaTime;
586 if (holdTimer > holdTime)
593 holdTimer += deltaTime;
594 if (holdTimer > holdTime)
601 holdTimer += deltaTime;
602 if (holdTimer > holdTime)
612 float moveSpeed = 600f * zoom;
613 float moveSpeedDeltaTime = (float)(moveSpeed * deltaTime);
614 Vector2 viewOffsetMove = Vector2.Zero;
617 viewOffsetMove.Y += moveSpeedDeltaTime;
621 viewOffsetMove.Y -= moveSpeedDeltaTime;
625 viewOffsetMove.X += moveSpeedDeltaTime;
629 viewOffsetMove.X -= moveSpeedDeltaTime;
632 viewAreaOffset += viewOffsetMove.ToPoint();
636 private double holdTimer;
637 private readonly
float holdTime = 0.2f;
638 private void Nudge(Keys key)
643 foreach (var sprite
in selectedSprites)
645 var newRect = sprite.SourceRect;
654 UpdateSourceRect(sprite, newRect);
658 foreach (var sprite
in selectedSprites)
660 var newRect = sprite.SourceRect;
669 UpdateSourceRect(sprite, newRect);
673 foreach (var sprite
in selectedSprites)
675 var newRect = sprite.SourceRect;
684 UpdateSourceRect(sprite, newRect);
688 foreach (var sprite
in selectedSprites)
690 var newRect = sprite.SourceRect;
699 UpdateSourceRect(sprite, newRect);
706 public override void Draw(
double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
708 graphics.Clear(backgroundColor);
709 spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState:
GameMain.
ScissorTestEnable, samplerState: SamplerState.PointClamp);
711 var viewArea = GetViewArea;
713 if (SelectedTexture !=
null)
716 (
int)(viewArea.Center.X - SelectedTexture.Bounds.Width / 2f * zoom),
717 (
int)(viewArea.Center.Y - SelectedTexture.Bounds.Height / 2f * zoom),
718 (
int)(SelectedTexture.Bounds.Width * zoom),
719 (
int)(SelectedTexture.Bounds.Height * zoom));
721 spriteBatch.Draw(SelectedTexture,
722 viewArea.Center.ToVector2(),
723 sourceRectangle:
null,
726 origin:
new Vector2(SelectedTexture.Bounds.Width / 2.0f, SelectedTexture.Bounds.Height / 2.0f),
728 effects: SpriteEffects.None,
732 GUI.DrawRectangle(spriteBatch, textureRect, Color.Gray, isFilled:
false);
742 if (sprite.FullPath != selectedTexturePath) {
continue; }
745 textureRect.X + (
int)(sprite.SourceRect.X * zoom),
746 textureRect.Y + (
int)(sprite.SourceRect.Y * zoom),
747 (
int)(sprite.SourceRect.Width * zoom),
748 (
int)(sprite.SourceRect.Height * zoom));
750 bool isSelected = selectedSprites.Contains(sprite);
751 GUI.DrawRectangle(spriteBatch, sourceRect, isSelected ? GUIStyle.Orange : GUIStyle.Red * 0.5f, thickness: isSelected ? 2 : 1);
753 Identifier
id = sprite.Identifier;
757 Vector2 GetTopLeft() => sprite.SourceRect.Location.ToVector2();
758 Vector2 GetTopRight() =>
new Vector2(GetTopLeft().X + sprite.SourceRect.Width, GetTopLeft().Y);
759 Vector2 GetBottomRight() =>
new Vector2(GetTopRight().X, GetTopRight().Y + sprite.SourceRect.Height);
760 var originWidget = GetWidget($
"{id}_origin", sprite, widgetSize,
WidgetShape.Cross, initMethod: w =>
762 w.Tooltip = TextManager.AddPunctuation(
':', originLabel, sprite.RelativeOrigin.FormatDoubleDecimal());
763 w.MouseHeld += dTime =>
765 w.DrawPos = PlayerInput.MousePosition.Clamp(textureRect.Location.ToVector2() + GetTopLeft() * zoom, textureRect.Location.ToVector2() + GetBottomRight() * zoom);
766 sprite.Origin = (w.DrawPos - textureRect.Location.ToVector2() - sprite.SourceRect.Location.ToVector2() * zoom) / zoom;
767 w.Tooltip = TextManager.AddPunctuation(
':', originLabel, sprite.RelativeOrigin.FormatDoubleDecimal());
770 w.DrawPos = (textureRect.Location.ToVector2() + (sprite.Origin + sprite.SourceRect.Location.ToVector2()) * zoom)
771 .Clamp(textureRect.Location.ToVector2() + GetTopLeft() * zoom, textureRect.Location.ToVector2() + GetBottomRight() * zoom);
773 var positionWidget = GetWidget($
"{id}_position", sprite, widgetSize,
WidgetShape.Rectangle, initMethod: w =>
775 w.Tooltip = positionLabel + sprite.SourceRect.Location;
776 w.MouseHeld += dTime =>
778 w.DrawPos = (drawGrid && snapToGrid) ?
779 SnapToGrid(PlayerInput.MousePosition, textureRect, zoom, Submarine.GridSize, Submarine.GridSize.X / 4.0f * zoom) :
780 PlayerInput.MousePosition;
781 w.DrawPos = new Vector2((float)Math.Ceiling(w.DrawPos.X), (float)Math.Ceiling(w.DrawPos.Y));
782 sprite.SourceRect = new Rectangle(((w.DrawPos - textureRect.Location.ToVector2()) / zoom).ToPoint(), sprite.SourceRect.Size);
783 if (spriteList.SelectedComponent is GUITextBlock textBox)
786 textBox.Text = GetSpriteName(sprite) +
" " + sprite.SourceRect;
788 w.Tooltip = positionLabel + sprite.SourceRect.Location;
790 w.Refresh = () => w.DrawPos = textureRect.Location.ToVector2() + sprite.SourceRect.Location.ToVector2() * zoom;
792 var sizeWidget = GetWidget($
"{id}_size", sprite, widgetSize,
WidgetShape.Rectangle, initMethod: w =>
794 w.Tooltip = TextManager.AddPunctuation(
':', sizeLabel, sprite.SourceRect.Size.ToString());
795 w.MouseHeld += dTime =>
797 w.DrawPos = (drawGrid && snapToGrid) ?
798 SnapToGrid(PlayerInput.MousePosition, textureRect, zoom, Submarine.GridSize, Submarine.GridSize.X / 4.0f * zoom) :
799 PlayerInput.MousePosition;
800 w.DrawPos = new Vector2((float)Math.Ceiling(w.DrawPos.X), (float)Math.Ceiling(w.DrawPos.Y));
801 sprite.SourceRect = new Rectangle(sprite.SourceRect.Location, ((w.DrawPos - positionWidget.DrawPos) / zoom).ToPoint());
803 sprite.RelativeOrigin = sprite.RelativeOrigin;
804 if (spriteList.SelectedComponent is GUITextBlock textBox)
807 textBox.Text = GetSpriteName(sprite) +
" " + sprite.SourceRect;
809 w.Tooltip = TextManager.AddPunctuation(
':', sizeLabel, sprite.SourceRect.Size.ToString());
811 w.Refresh = () => w.DrawPos = textureRect.Location.ToVector2() +
new Vector2(sprite.SourceRect.Right, sprite.SourceRect.Bottom) * zoom;
813 originWidget.MouseDown += () => GUI.KeyboardDispatcher.Subscriber =
null;
814 positionWidget.MouseDown += () => GUI.KeyboardDispatcher.Subscriber =
null;
815 sizeWidget.MouseDown += () => GUI.KeyboardDispatcher.Subscriber =
null;
818 positionWidget.Draw(spriteBatch, (
float)deltaTime);
819 sizeWidget.Draw(spriteBatch, (
float)deltaTime);
820 originWidget.Draw(spriteBatch, (
float)deltaTime);
826 GUI.Draw(Cam, spriteBatch);
831 private void DrawGrid(SpriteBatch spriteBatch, Rectangle gridArea,
float zoom, Vector2 gridSize)
834 if (gridSize.X < 1.0f) {
return; }
835 if (gridSize.Y < 1.0f) {
return; }
836 int xLines = (int)(gridArea.Width / gridSize.X);
837 int yLines = (int)(gridArea.Height / gridSize.Y);
839 for (
int x = 0; x <= xLines; x++)
841 GUI.DrawLine(spriteBatch,
842 new Vector2(gridArea.X + x * gridSize.X, gridArea.Y),
843 new Vector2(gridArea.X + x * gridSize.X, gridArea.Bottom),
844 Color.White * 0.25f);
846 for (
int y = 0; y <= yLines; y++)
848 GUI.DrawLine(spriteBatch,
849 new Vector2(gridArea.X, gridArea.Y + y * gridSize.Y),
850 new Vector2(gridArea.Right, gridArea.Y + y * gridSize.Y),
851 Color.White * 0.25f);
855 private Vector2 SnapToGrid(Vector2 position, Rectangle gridArea,
float zoom, Vector2 gridSize,
float tolerance)
858 if (gridSize.X < 1.0f) {
return position; }
859 if (gridSize.Y < 1.0f) {
return position; }
861 Vector2 snappedPos = position;
862 snappedPos.X -= gridArea.X;
863 snappedPos.Y -= gridArea.Y;
865 Vector2 gridPos =
new Vector2(
866 MathUtils.RoundTowardsClosest(snappedPos.X, gridSize.X),
867 MathUtils.RoundTowardsClosest(snappedPos.Y, gridSize.Y));
869 if (Math.Abs(gridPos.X - snappedPos.X) < tolerance)
871 snappedPos.X = gridPos.X;
873 if (Math.Abs(gridPos.Y - snappedPos.Y) < tolerance)
875 snappedPos.Y = gridPos.Y;
878 snappedPos.X += gridArea.X;
879 snappedPos.Y += gridArea.Y;
883 private void FilterTextures(
string text)
885 if (
string.IsNullOrWhiteSpace(text))
887 filterTexturesLabel.
Visible =
true;
891 text = text.ToLower();
892 filterTexturesLabel.
Visible =
false;
895 if (!(child is GUITextBlock textBlock)) {
continue; }
896 textBlock.Visible = textBlock.Text.Contains(text, StringComparison.OrdinalIgnoreCase);
899 private void FilterSprites(
string text)
901 if (
string.IsNullOrWhiteSpace(text))
903 filterSpritesLabel.
Visible =
true;
907 text = text.ToLower();
908 filterSpritesLabel.
Visible =
false;
911 if (!(child is GUITextBlock textBlock)) {
continue; }
912 textBlock.Visible = textBlock.Text.Contains(text, StringComparison.OrdinalIgnoreCase);
926 loadedSprites.ForEach(s => s.Remove());
927 loadedSprites.Clear();
930 var reloadedSprites =
new List<Sprite>();
931 foreach (var sprite
in dirtySprites)
935 if (s.FullPath == sprite.FullPath && !reloadedSprites.Contains(s))
938 reloadedSprites.Add(s);
942 dirtySprites.Clear();
943 filterSpritesBox.
Text =
"";
944 filterTexturesBox.
Text =
"";
950 if (!loadedSprites.Contains(sprite))
952 loadedSprites.Add(sprite);
955 if (selectedSprites.Any(s => s.FullPath != selectedTexturePath))
959 if (Widget.EnableMultiSelect)
961 if (selectedSprites.Contains(sprite))
963 selectedSprites.Remove(sprite);
967 selectedSprites.Add(sprite);
968 dirtySprites.Add(sprite);
973 selectedSprites.Clear();
974 selectedSprites.Add(sprite);
975 dirtySprites.Add(sprite);
977 if (sprite.
FullPath != selectedTexturePath)
980 UpdateScrollBar(textureList);
982 xmlPathText.
Text =
string.Empty;
983 foreach (var s
in selectedSprites)
985 texturePathText.
Text = s.FilePath.Value;
986 var element = s.SourceElement;
989 string xmlPath = element.ParseContentPathFromUri();
992 xmlPathText.
Text +=
"\n" + xmlPath;
1001 selectedSprites.Clear();
1005 HashSet<string> textures =
new HashSet<string>();
1007 foreach (
Sprite sprite
in loadedSprites.OrderBy(s => Path.GetFileNameWithoutExtension(s.FilePath.Value)))
1010 if (sprite.FilePath.IsNullOrEmpty()) {
continue; }
1011 string normalizedFilePath = sprite.FilePath.FullPath;
1012 if (!textures.Contains(normalizedFilePath))
1015 Path.GetFileName(sprite.FilePath.Value))
1017 ToolTip = sprite.FilePath.Value,
1018 UserData = sprite.FullPath
1020 textures.Add(normalizedFilePath);
1026 foreach (
Sprite sprite
in loadedSprites.OrderBy(s => s.SourceElement.GetAttributeContentPath(
"texture")?.Value ??
string.Empty))
1028 string elementLocalName = sprite.SourceElement.Element.Name.LocalName;
1029 string text = $
"{GetSpriteName(sprite)} ({sprite.SourceRect.X}, {sprite.SourceRect.Y}, {sprite.SourceRect.Width}, {sprite.SourceRect.Height}) [{elementLocalName}]";
1030 if (
string.Equals(elementLocalName,
"sprite", StringComparison.InvariantCultureIgnoreCase))
1032 text = $
"{GetSpriteName(sprite)} ({sprite.SourceRect.X}, {sprite.SourceRect.Y}, {sprite.SourceRect.Width}, {sprite.SourceRect.Height})";
1040 topPanelContents.
Visible =
false;
1045 if (SelectedTexture ==
null) {
return; }
1046 var viewArea = GetViewArea;
1047 float width = viewArea.Width / (float)SelectedTexture.Width;
1048 float height = viewArea.Height / (
float)SelectedTexture.Height;
1049 zoom = Math.Min(1, Math.Min(width, height));
1050 zoomBar.
BarScroll = GetBarScrollValue();
1051 viewAreaOffset = Point.Zero;
1056 private Point viewAreaOffset;
1062 var viewArea =
new Rectangle(leftPanel.
Rect.Right + margin + viewAreaOffset.X, topPanel.
Rect.Bottom + margin + viewAreaOffset.Y, rightPanel.
Rect.Left - leftPanel.
Rect.Right - margin * 2, Frame.Rect.Height - topPanel.
Rect.Height - margin * 2);
1067 private float GetBarScrollValue() => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(MinZoom, MaxZoom, zoom));
1069 private string GetSpriteName(Sprite sprite)
1071 var sourceElement = sprite.SourceElement;
1072 if (sourceElement ==
null) {
return string.Empty; }
1073 string name = sprite.Name;
1074 if (
string.IsNullOrWhiteSpace(name))
1076 name = sourceElement.Parent?.GetAttributeString(
"identifier",
string.Empty);
1078 if (
string.IsNullOrEmpty(name))
1080 name = sourceElement.Parent?.GetAttributeString(
"name",
string.Empty);
1082 return string.IsNullOrEmpty(name) ? Path.GetFileNameWithoutExtension(sprite.FilePath.Value) : name;
1085 private void UpdateScrollBar(GUIListBox listBox)
1087 var sb = listBox.ScrollBar;
1088 sb.BarScroll = MathHelper.Clamp(MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, listBox.Content.CountChildren - 1, listBox.SelectedIndex)), sb.MinValue, sb.MaxValue);
1091 private void UpdateSourceRect(Sprite sprite, Rectangle newRect)
1093 sprite.SourceRect = newRect;
1095 sprite.RelativeOrigin = sprite.RelativeOrigin;
1100 private Dictionary<string, Widget> widgets =
new Dictionary<string, Widget>();
1102 private Widget GetWidget(
string id, Sprite sprite,
int size = 5, WidgetShape shape =
WidgetShape.Rectangle, Action<Widget> initMethod =
null)
1104 if (!widgets.TryGetValue(
id, out Widget widget))
1106 int selectedSize = (int)Math.Round(size * 1.5f);
1107 widget =
new Widget(
id, size, shape)
1110 Color = Color.Yellow,
1111 SecondaryColor = Color.Gray,
1112 TooltipOffset =
new Vector2(selectedSize / 2 + 5, -10)
1114 widget.PreDraw += (sp, dTime) =>
1116 if (!widget.IsControlled)
1121 widget.PreUpdate += dTime => widget.Enabled = selectedSprites.Contains(sprite);
1122 widget.PostUpdate += dTime =>
1124 widget.InputAreaMargin = widget.IsControlled ? 1000 : 0;
1125 widget.Size = widget.IsSelected ? selectedSize : size;
1126 widget.IsFilled = widget.IsControlled;
1128 widgets.Add(
id, widget);
1129 initMethod?.Invoke(widget);
1134 private void ResetWidgets()
1137 Widget.SelectedWidgets.Clear();
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void Select(object userData, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled)
override void ClearChildren()
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
static RasterizerState ScissorTestEnable
Action ResolutionChanged
NOTE: Use very carefully. You need to ensure that you ALWAYS unsubscribe from this when you no longer...
bool Contains(string str, StringComparison stringComparison=StringComparison.Ordinal)
override void Update(double deltaTime)
override void DeselectEditorSpecific()
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
void SelectSprite(Sprite sprite)
override void AddToGUIUpdateList()
By default, submits the screen's main GUIFrame and, if requested upon construction,...
static IEnumerable< Sprite > LoadedSprites
static readonly Vector2 GridSize