Client LuaCsForBarotrauma
EditorImage.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using Barotrauma.IO;
5 using System.Linq;
6 using System.Xml.Linq;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 using Microsoft.Xna.Framework.Input;
10 
11 namespace Barotrauma
12 {
14  {
15  private struct EditorImageContainer
16  {
17  public float Rotation;
18  public float Scale;
19  public Vector2 Position;
20  public string Path;
21  public float Opacity;
22  public EditorImage.DrawTargetType DrawTarget;
23 
24  public EditorImage CreateImage()
25  {
26  return new EditorImage(Path, Position)
27  {
28  Position = Position,
29  Scale = Scale,
30  Opacity = Opacity,
31  Rotation = Rotation,
32  DrawTarget = DrawTarget
33  };
34  }
35 
36  public static EditorImageContainer? Load(XElement element)
37  {
38  string path = element.GetAttributeString("path", "");
39  if (string.IsNullOrWhiteSpace(path)) { return null; }
40 
41  Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
42  float scale = element.GetAttributeFloat("scale", 1f);
43  float rotation = element.GetAttributeFloat("rotation", 0f);
44  float opacity = element.GetAttributeFloat("opacity", 1f);
45  string drawTargetString = element.GetAttributeString("drawtarget", "");
46  if (!Enum.TryParse<EditorImage.DrawTargetType>(drawTargetString, out var drawTarget))
47  {
48  drawTarget = EditorImage.DrawTargetType.World;
49  }
50 
51  return new EditorImageContainer
52  {
53  Path = path,
54  Rotation = rotation,
55  Opacity = opacity,
56  Position = pos,
57  Scale = scale,
58  DrawTarget = drawTarget
59  };
60  }
61 
62  public static EditorImageContainer ImageToContainer(EditorImage img)
63  {
64  return new EditorImageContainer
65  {
66  Path = img.ImagePath,
67  Rotation = img.Rotation,
68  Position = img.Position,
69  Opacity = img.Opacity,
70  Scale = img.Scale,
71  DrawTarget = img.DrawTarget
72  };
73  }
74 
75  public static XElement SerializeImage(EditorImageContainer image)
76  {
77  return new XElement("image",
78  new XAttribute("pos", XMLExtensions.Vector2ToString(image.Position)),
79  new XAttribute("rotation", image.Rotation),
80  new XAttribute("opacity", image.Opacity),
81  new XAttribute("path", image.Path),
82  new XAttribute("scale", image.Scale),
83  new XAttribute("drawtarget", image.DrawTarget.ToString()));
84  }
85  }
86 
87  private readonly List<EditorImageContainer> PendingImages = new List<EditorImageContainer>();
88 
89  public readonly List<EditorImage> Images = new List<EditorImage>();
90 
91  private readonly List<EditorImage> screenImages = new List<EditorImage>(),
92  worldImages = new List<EditorImage>();
93 
94  public bool EditorMode;
95 
96  private LocalizedString editModeText = "";
97  private Vector2 textSize = Vector2.Zero;
98 
99  public void Save(XElement element)
100  {
101  XElement saveElement = new XElement("editorimages");
102  foreach (EditorImage image in Images)
103  {
104  EditorImageContainer container = EditorImageContainer.ImageToContainer(image);
105  saveElement.Add(EditorImageContainer.SerializeImage(container));
106  }
107 
108  foreach (EditorImageContainer container in PendingImages)
109  {
110  saveElement.Add(EditorImageContainer.SerializeImage(container));
111  }
112 
113  element.Add(saveElement);
114  }
115 
116  public void Load(XElement element)
117  {
118  Clear(alsoPending: true);
119 
120  foreach (var subElement in element.Elements())
121  {
122  EditorImageContainer? tempImage = EditorImageContainer.Load(subElement);
123  if (tempImage != null)
124  {
125  PendingImages.Add(tempImage.Value);
126  }
127  }
128  }
129 
130  public void OnEditorSelected()
131  {
132  editModeText = TextManager.Get("SubEditor.ImageEditingMode");
133  textSize = GUIStyle.LargeFont.MeasureString(editModeText);
134 
135  TryLoadPendingImages();
136  }
137 
138  private void TryLoadPendingImages()
139  {
140  if (PendingImages.Count == 0) { return; }
141 
142  Clear(alsoPending: false);
143 
144  foreach (EditorImageContainer pendingImage in PendingImages)
145  {
146  EditorImage img = pendingImage.CreateImage();
147  if (img.Image == null) { continue; }
148  Images.Add(img);
149  img.UpdateRectangle();
150  }
151 
152  UpdateImageCategories();
153  PendingImages.Clear();
154  }
155 
156  public void Clear(bool alsoPending = false)
157  {
158  foreach (EditorImage img in Images)
159  {
160  img.Image?.Dispose();
161  }
162 
163  Images.Clear();
164  screenImages.Clear();
165  worldImages.Clear();
166  if (alsoPending)
167  {
168  PendingImages.Clear();
169  }
170  }
171 
172  public void Update(float deltaTime)
173  {
174  if (!EditorMode) { return; }
175 
176  foreach (EditorImage image in Images)
177  {
178  image.Update(deltaTime);
179  }
180 
182  {
183  EditorImage? hover = Images.FirstOrDefault(img => img.IsMouseOn());
184  if (hover != null)
185  {
186  foreach (EditorImage image in Images)
187  {
188  image.Selected = false;
189  }
190 
191  hover.Selected = true;
192  }
193  }
194 
195  if (PlayerInput.KeyHit(Keys.Delete) || (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.D)))
196  {
197  Images.RemoveAll(img => img.Selected);
198  UpdateImageCategories();
199  }
200 
201  if (PlayerInput.KeyHit(Keys.Space))
202  {
203  foreach (EditorImage image in Images)
204  {
205  if (image.Selected)
206  {
207  if (image.DrawTarget == EditorImage.DrawTargetType.World)
208  {
209  Vector2 pos = image.Position;
210  pos.Y = -pos.Y;
211  pos = Screen.Selected.Cam.WorldToScreen(pos);
212  if (PlayerInput.IsShiftDown())
213  {
214  pos = new Vector2(GameMain.GraphicsWidth / 2f, GameMain.GraphicsHeight / 2f);
215  }
216 
217  image.Position = pos;
218  image.DrawTarget = EditorImage.DrawTargetType.Camera;
219  image.Scale *= Screen.Selected.Cam.Zoom;
220  image.UpdateRectangle();
221  }
222  else
223  {
224  Vector2 pos = Screen.Selected.Cam.ScreenToWorld(image.Position);
225  pos.Y = -pos.Y;
226  image.Position = pos;
227  image.DrawTarget = EditorImage.DrawTargetType.World;
228  image.Scale /= Screen.Selected.Cam.Zoom;
229  image.UpdateRectangle();
230  }
231  }
232  }
233 
234  UpdateImageCategories();
235  }
236 
237  MapEntity.DisableSelect = true;
238  }
239 
240  private void UpdateImageCategories()
241  {
242  screenImages.Clear();
243  worldImages.Clear();
244 
245  foreach (EditorImage image in Images)
246  {
247  switch (image.DrawTarget)
248  {
249  case EditorImage.DrawTargetType.World:
250  worldImages.Add(image);
251  break;
252  default:
253  screenImages.Add(image);
254  break;
255  }
256  }
257  }
258 
259  public void CreateImageWizard()
260  {
261  string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
262  if (!Directory.Exists(home)) { return; }
263 
264  FileSelection.OnFileSelected = file =>
265  {
267  pos.Y = -pos.Y;
268  Images.Add(new EditorImage(file, pos) { DrawTarget = EditorImage.DrawTargetType.World });
269  UpdateImageCategories();
270  GameSettings.SaveCurrentConfig();
271  };
272 
273  FileSelection.ClearFileTypeFilters();
274  FileSelection.AddFileTypeFilter("PNG", "*.png");
275  FileSelection.AddFileTypeFilter("JPEG", "*.jpg, *.jpeg");
276  FileSelection.AddFileTypeFilter("All files", "*.*");
277  FileSelection.SelectFileTypeFilter("*.png");
278  FileSelection.CurrentDirectory = home;
279  FileSelection.Open = true;
280  }
281 
282  public void DrawEditing(SpriteBatch spriteBatch, Camera cam)
283  {
284  if (!EditorMode) { return; }
285 
286  DrawImages(spriteBatch, cam);
287 
288  spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
289  Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2f - (textSize.X / 2f), GameMain.GraphicsHeight / 10f - (textSize.Y / 2f));
290  GUI.DrawString(spriteBatch, textPos, editModeText, GUIStyle.Yellow, Color.Black * 0.4f, 8, GUIStyle.LargeFont);
291  spriteBatch.End();
292  }
293 
294  public void Draw(SpriteBatch spriteBatch, Camera cam)
295  {
296  if (EditorMode) { return; }
297 
298  DrawImages(spriteBatch, cam);
299  }
300 
301  private void DrawImages(SpriteBatch spriteBatch, Camera cam)
302  {
303  if (screenImages.Count > 0)
304  {
305  spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
306  foreach (EditorImage image in screenImages)
307  {
308  image.Draw(spriteBatch);
309  if (EditorMode) { image.DrawEditing(spriteBatch, cam); }
310  }
311 
312  spriteBatch.End();
313  }
314 
315  if (worldImages.Count > 0)
316  {
317  spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, transformMatrix: cam.Transform);
318  foreach (EditorImage image in worldImages)
319  {
320  image.Draw(spriteBatch);
321  if (EditorMode) { image.DrawEditing(spriteBatch, cam); }
322  }
323 
324  spriteBatch.End();
325  }
326  }
327  }
328 
330  {
331  public enum DrawTargetType
332  {
333  Camera,
334  World
335  }
336 
337  public Texture2D? Image;
338  public string ImagePath;
339  public Vector2 Position;
340  public float Rotation;
341  public float Opacity = 1f;
342  public float Scale = 1f;
344  public bool Selected;
345 
347  private float prevAngle;
348  private bool disableMove;
349  private bool isDragging;
350 
351  private readonly Dictionary<string, Widget> widgets = new Dictionary<string, Widget>();
352 
353  public EditorImage(string path, Vector2 pos)
354  {
355  Image = Sprite.LoadTexture(path, compress: false);
356  ImagePath = path;
357  Position = pos;
358  UpdateRectangle();
359  }
360 
361  public bool IsMouseOn() => Bounds.Contains(GetMousePos());
362 
363  public Vector2 GetMousePos()
364  {
365  switch (DrawTarget)
366  {
367  case DrawTargetType.Camera:
368  return PlayerInput.MousePosition;
369  case DrawTargetType.World:
371  pos.Y = -pos.Y;
372  return pos;
373  default:
374  return PlayerInput.MousePosition;
375  }
376  }
377 
378  public void Update(float deltaTime)
379  {
380  if (!Selected) { return; }
381 
382  if (widgets.Values.Any(w => w.IsSelected)) { return; }
383 
384  if (PlayerInput.PrimaryMouseButtonDown() && !disableMove && IsMouseOn())
385  {
386  isDragging = true;
387  }
388 
389  if (isDragging)
390  {
391  Camera cam = Screen.Selected.Cam;
392  if (PlayerInput.MouseSpeed != Vector2.Zero)
393  {
394  Vector2 mouseSpeed = PlayerInput.MouseSpeed;
395  if (DrawTarget == DrawTargetType.World)
396  {
397  mouseSpeed /= cam.Zoom;
398  }
399 
400  Position += mouseSpeed;
401  UpdateRectangle();
402  }
403  }
404 
405  if (PlayerInput.KeyDown(Keys.OemPlus) || PlayerInput.KeyDown(Keys.Up))
406  {
407  Opacity += 0.01f;
408  }
409 
410  if (PlayerInput.KeyDown(Keys.OemMinus) || PlayerInput.KeyDown(Keys.Down))
411  {
412  Opacity -= 0.01f;
413  }
414 
415  if (PlayerInput.KeyHit(Keys.D0))
416  {
417  Opacity = 1f;
418  }
419 
420  Opacity = Math.Clamp(Opacity, 0, 1f);
421 
423  {
424  isDragging = false;
425  }
426  }
427 
428  private void DrawWidgets(SpriteBatch spriteBatch)
429  {
430  float widgetSize = Image == null ? 100f : Math.Max(Image.Width, Image.Height) / 2f;
431 
432  int width = 3;
433  int size = 32;
434  if (DrawTarget == DrawTargetType.World)
435  {
436  width = Math.Max(width, (int) (width / Screen.Selected.Cam.Zoom));
437  }
438 
439  Widget currentWidget = GetWidget("transform", size, width, widget =>
440  {
441  widget.MouseDown += () =>
442  {
443  widget.Color = GUIStyle.Green;
444  prevAngle = Rotation;
445  disableMove = true;
446  };
447  widget.Deselected += () =>
448  {
449  widget.Color = Color.Yellow;
450  disableMove = false;
451  };
452  widget.MouseHeld += (deltaTime) =>
453  {
454  Rotation = GetRotationAngle(Position) + (float) Math.PI / 2f;
455  float distance = Vector2.Distance(Position, GetMousePos());
456  Scale = Math.Abs(distance) / widgetSize;
457  if (PlayerInput.IsShiftDown())
458  {
459  const float rotationStep = (float) (Math.PI / 4f);
460  Rotation = (float) Math.Round(Rotation / rotationStep) * rotationStep;
461  }
462 
463  if (PlayerInput.IsCtrlDown())
464  {
465  const float scaleStep = 0.1f;
466  Scale = (float) Math.Round(Scale / scaleStep) * scaleStep;
467  }
468 
469  UpdateRectangle();
470  };
471  widget.PreUpdate += (deltaTime) =>
472  {
473  if (DrawTarget != DrawTargetType.World) { return; }
474 
475  widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y);
476  widget.DrawPos = Screen.Selected.Cam.WorldToScreen(widget.DrawPos);
477  };
478  widget.PostUpdate += (deltaTime) =>
479  {
480  if (DrawTarget != DrawTargetType.World) { return; }
481 
482  widget.DrawPos = Screen.Selected.Cam.ScreenToWorld(widget.DrawPos);
483  widget.DrawPos = new Vector2(widget.DrawPos.X, -widget.DrawPos.Y);
484  };
485  widget.PreDraw += (sprtBtch, deltaTime) =>
486  {
487  widget.Tooltip = $"Scale: {Math.Round(Scale, 2)}\n" +
488  $"Rotation: {(int) MathHelper.ToDegrees(Rotation)}";
489  float rotation = Rotation - (float) Math.PI / 2f;
490  widget.DrawPos = Position + new Vector2((float) Math.Cos(rotation), (float) Math.Sin(rotation)) * (Scale * widgetSize);
491  widget.Update(deltaTime);
492  };
493  });
494 
495  currentWidget.Draw(spriteBatch, (float) Timing.Step);
496  GUI.DrawLine(spriteBatch, Position, currentWidget.DrawPos, GUIStyle.Green, width: width);
497  }
498 
499  private float GetRotationAngle(Vector2 drawPosition)
500  {
501  Vector2 rotationVector = GetMousePos() - drawPosition;
502  rotationVector.Normalize();
503  double angle = Math.Atan2(MathHelper.ToRadians(rotationVector.Y), MathHelper.ToRadians(rotationVector.X));
504  if (angle < 0)
505  {
506  angle = Math.Abs(angle - prevAngle) < Math.Abs((angle + Math.PI * 2) - prevAngle) ? angle : angle + Math.PI * 2;
507  }
508  else if (angle > 0)
509  {
510  angle = Math.Abs(angle - prevAngle) < Math.Abs((angle - Math.PI * 2) - prevAngle) ? angle : angle - Math.PI * 2;
511  }
512 
513  angle = MathHelper.Clamp((float) angle, -((float) Math.PI * 2), (float) Math.PI * 2);
514  prevAngle = (float) angle;
515  return (float) angle;
516  }
517 
518  private Widget GetWidget(string id, int size, float thickness = 1f, Action<Widget>? initMethod = null)
519  {
520  if (!widgets.TryGetValue(id, out Widget? widget))
521  {
522  widget = new Widget(id, size, WidgetShape.Rectangle)
523  {
524  Color = Color.Yellow,
525  RequireMouseOn = false
526  };
527  widgets.Add(id, widget);
528  initMethod?.Invoke(widget);
529  }
530 
531  widget.Size = size;
532  widget.Thickness = thickness;
533  return widget;
534  }
535 
536  public void UpdateRectangle()
537  {
538  if (Image == null)
539  {
540  Bounds = new Rectangle((int) Position.X, (int) Position.Y, 512, 512);
541  return;
542  }
543 
544  Vector2 size = new Vector2(Image.Width * Scale, Image.Height * Scale);
545  Bounds = new Rectangle((Position - size / 2f).ToPoint(), size.ToPoint());
546  }
547 
548  public void Draw(SpriteBatch spriteBatch)
549  {
550  if (Image == null) { return; }
551 
552  spriteBatch.Draw(Image, Position, null, Color.White * Opacity, Rotation, new Vector2(Image.Width / 2f, Image.Height / 2f), scale: Scale, SpriteEffects.None, 0f);
553  }
554 
555  public void DrawEditing(SpriteBatch spriteBatch, Camera cam)
556  {
557  Rectangle bounds = Bounds;
558  int width = 4;
559  if (DrawTarget == DrawTargetType.World)
560  {
561  width = (int) (width / cam.Zoom);
562  }
563 
564  GUI.DrawRectangle(spriteBatch, bounds, Selected ? GUIStyle.Red : GUIStyle.Green, thickness: width);
565  if (Selected)
566  {
567  DrawWidgets(spriteBatch);
568  }
569  }
570  }
571 }
Vector2 WorldToScreen(Vector2 coords)
Definition: Camera.cs:416
float? Zoom
Definition: Camera.cs:78
Matrix Transform
Definition: Camera.cs:136
Vector2 ScreenToWorld(Vector2 coords)
Definition: Camera.cs:410
EditorImage(string path, Vector2 pos)
Definition: EditorImage.cs:353
DrawTargetType DrawTarget
Definition: EditorImage.cs:343
void Update(float deltaTime)
Definition: EditorImage.cs:378
void DrawEditing(SpriteBatch spriteBatch, Camera cam)
Definition: EditorImage.cs:555
void Draw(SpriteBatch spriteBatch)
Definition: EditorImage.cs:548
void Clear(bool alsoPending=false)
Definition: EditorImage.cs:156
void Save(XElement element)
Definition: EditorImage.cs:99
void Update(float deltaTime)
Definition: EditorImage.cs:172
readonly List< EditorImage > Images
Definition: EditorImage.cs:89
void Draw(SpriteBatch spriteBatch, Camera cam)
Definition: EditorImage.cs:294
void Load(XElement element)
Definition: EditorImage.cs:116
void DrawEditing(SpriteBatch spriteBatch, Camera cam)
Definition: EditorImage.cs:282
static int GraphicsWidth
Definition: GameMain.cs:162
static int GraphicsHeight
Definition: GameMain.cs:168
static bool KeyDown(InputType inputType)
WidgetShape
Definition: Widget.cs:11