using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using OpenTK.Graphics.OpenGL; using OpenTK; using Toolbox.Library.Forms; using Toolbox.Library; using Toolbox.Library.Rendering; using Toolbox.Library.IO; namespace LayoutBXLYT { public partial class LayoutViewer : LayoutControlDocked { public LayoutUndoManager UndoManger = new LayoutUndoManager(); public List SelectedPanes = new List(); public Camera2D Camera = new Camera2D(); public class Camera2D { public Matrix4 ModelViewMatrix => ModelMatrix * ViewMatrix * ProjectionMatrix; public Matrix4 ProjectionMatrix = Matrix4.Identity; public Matrix4 ViewMatrix = Matrix4.Identity; public Matrix4 ModelMatrix = Matrix4.Identity; public float Zoom = 1; public Vector2 Position; } private LayoutEditor ParentEditor; private List CopiedPanes = new List(); private RenderableTex backgroundTex; public BxlytHeader LayoutFile; public List LayoutFiles = new List(); private Dictionary Textures; private void glControl1_Load(object sender, EventArgs e) { } public void ResetCamera() { Camera = new Camera2D(); } public void ResetLayout(BxlytHeader bxlyt) { LayoutFile = bxlyt; UpdateViewport(); } public Dictionary GetTextures() { return Textures; } public GLControl GetGLControl() => glControl1; public LayoutViewer(LayoutEditor editor, BxlytHeader bxlyt, Dictionary textures) { InitializeComponent(); ParentEditor = editor; Text = bxlyt.FileName; Textures = textures; LoadLayout(bxlyt); } public void LoadLayout(BxlytHeader bxlyt) { LayoutFile = bxlyt; LayoutFiles.Add(bxlyt); if (bxlyt.Textures.Count > 0) { var textures = bxlyt.GetTextures; foreach (var tex in bxlyt.Textures) { //Some games use different cases for archives string lowerCase = tex.ToLower(); if (textures.ContainsKey(tex)) AddTexture(tex, textures[tex]); else if (textures.ContainsKey(lowerCase)) AddTexture(tex, textures[lowerCase]); } } } private void AddTexture(string name, STGenericTexture tex) { if (!Textures.ContainsKey(name)) Textures.Add(name, tex); } public override void OnControlClosing() { foreach (var tex in LayoutFile.Textures) { if (Textures.ContainsKey(tex)) { Textures[tex].DisposeRenderable(); Textures.Remove(tex); } } } public void UpdateViewport() { glControl1.Invalidate(); } private void glControl1_Paint(object sender, PaintEventArgs e) { if (!Runtime.OpenTKInitialized) return; glControl1.Context.MakeCurrent(glControl1.WindowInfo); OnRender(); } private BxlytShader GlobalShader; public bool GameWindow = false; public bool UseOrtho => Runtime.LayoutEditor.UseOrthographicView; private Color BackgroundColor => Runtime.LayoutEditor.BackgroundColor; private void OnRender() { if (LayoutFile == null) return; if (!GameWindow) { if (ParentEditor != null) ParentEditor.GamePreviewWindow?.UpdateViewport(); } if (GameWindow) { RenderGameWindow(); RenderScene(); } else { RenderEditor(); RenderScene(); } } private void RenderGameWindow() { glControl1.MakeCurrent(); int WindowWidth = (int)LayoutFile.RootPane.Width; int WindowHeight = (int)LayoutFile.RootPane.Height; GL.Viewport(0, 0, glControl1.Width, glControl1.Height); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); if (UseOrtho) { float halfW = WindowWidth, halfH = WindowHeight; var orthoMatrix = Matrix4.CreateOrthographic(halfW, halfH, -10000, 10000); GL.LoadMatrix(ref orthoMatrix); GL.MatrixMode(MatrixMode.Modelview); Camera.ProjectionMatrix = orthoMatrix; } else { var cameraPosition = new Vector3(0, 0, -600); var perspectiveMatrix = Matrix4.CreateTranslation(cameraPosition) * Matrix4.CreatePerspectiveFieldOfView(0.785398f, WindowWidth / WindowHeight, 0.01f, 100000); GL.LoadMatrix(ref perspectiveMatrix); GL.MatrixMode(MatrixMode.Modelview); Camera.ProjectionMatrix = perspectiveMatrix; } GL.ClearColor(BackgroundColor); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); } private void RenderEditor() { glControl1.MakeCurrent(); GL.Viewport(0, 0, glControl1.Width, glControl1.Height); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); if (UseOrtho) { float halfW = glControl1.Width / 2.0f, halfH = glControl1.Height / 2.0f; var orthoMatrix = Matrix4.CreateOrthographic(halfW, halfH, -10000, 10000); GL.LoadMatrix(ref orthoMatrix); GL.MatrixMode(MatrixMode.Modelview); Camera.ProjectionMatrix = orthoMatrix; } else { var cameraPosition = new Vector3(Camera.Position.X, Camera.Position.Y, -(Camera.Zoom * 500)); var perspectiveMatrix = Matrix4.CreateTranslation(cameraPosition) * Matrix4.CreatePerspectiveFieldOfView(1.3f, glControl1.Width / glControl1.Height, 0.01f, 100000); GL.LoadMatrix(ref perspectiveMatrix); GL.MatrixMode(MatrixMode.Modelview); Camera.ProjectionMatrix = perspectiveMatrix; } GL.ClearColor(BackgroundColor); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); if (UseOrtho && !GameWindow) { GL.PushMatrix(); GL.Scale(Camera.Zoom, Camera.Zoom, 1); GL.Translate(Camera.Position.X, Camera.Position.Y, 0); Camera.ViewMatrix *= Matrix4.CreateScale(Camera.Zoom, Camera.Zoom, 1) * Matrix4.CreateTranslation(Camera.Position.X, Camera.Position.Y, 0); } } private void RenderScene(bool showSelectionBox = false) { // GL.Disable(EnableCap.CullFace); GL.Enable(EnableCap.Blend); GL.Enable(EnableCap.AlphaTest); GL.AlphaFunc(AlphaFunction.Always, 0f); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); GL.Enable(EnableCap.ColorMaterial); GL.Disable(EnableCap.Texture2D); GL.BindTexture(TextureTarget.Texture2D, 0); GL.BlendEquation(BlendEquationMode.FuncAdd); if (!GameWindow) { DrawRootPane(LayoutFile.RootPane); DrawGrid(); DrawXyLines(); } GL.BindTexture(TextureTarget.Texture2D, 0); GL.Disable(EnableCap.Texture2D); if (GlobalShader == null) { GlobalShader = new BxlytShader(); GlobalShader.Compile(); } bool PreviewHitbox = false; if (PreviewHitbox) { foreach (var file in LayoutFiles) { foreach (var pane in file.PaneLookup.Values) { if (!pane.Visible || !pane.DisplayInEditor && !pane.IsRoot) continue; //Hitbox debug var hitbox = pane.CreateRectangle(); hitbox = hitbox.GetTransformedRectangle(pane.Parent, pane.Translate, pane.Rotate, pane.Scale); GL.Begin(PrimitiveType.Quads); GL.Color4(Color.FromArgb(128, 255, 0, 0)); GL.Vertex2(hitbox.BottomLeftPoint); GL.Vertex2(hitbox.BottomRightPoint); GL.Vertex2(hitbox.TopRightPoint); GL.Vertex2(hitbox.TopLeftPoint); GL.End(); } } } foreach (var layout in LayoutFiles) RenderPanes(GlobalShader, layout.RootPane, true, 255, false, null, 0); Vector2 TopLeft = new Vector2(); Vector2 BottomRight = new Vector2(); GL.Disable(EnableCap.Texture2D); GL.Disable(EnableCap.AlphaTest); GL.Disable(EnableCap.Blend); GL.UseProgram(0); foreach (var pane in SelectedPanes) { var rect = pane.CreateRectangle(); TopLeft.X = Math.Min(TopLeft.X, rect.LeftPoint); TopLeft.Y = Math.Max(TopLeft.Y, rect.TopPoint); BottomRight.X = Math.Max(BottomRight.X, rect.RightPoint); BottomRight.Y = Math.Min(BottomRight.Y, rect.BottomPoint); if (pickAxis == PickAxis.Y) { GL.Begin(PrimitiveType.Lines); GL.Color4(Color.Green); GL.Vertex2(pane.Translate.X, -999999); GL.Vertex2(pane.Translate.X, 99999); GL.End(); } if (pickAxis == PickAxis.X) { GL.Begin(PrimitiveType.Lines); GL.Color4(Color.Red); GL.Vertex2(-999999, pane.Translate.Y); GL.Vertex2(99999, pane.Translate.Y); GL.End(); } } if (showSelectionBox) { GL.Begin(PrimitiveType.LineLoop); GL.Color4(Color.Red); GL.Vertex2(SelectionBox.BottomLeftPoint); GL.Vertex2(SelectionBox.BottomRightPoint); GL.Vertex2(SelectionBox.TopRightPoint); GL.Vertex2(SelectionBox.TopLeftPoint); GL.End(); } //Create a bounding box for all selected panes //This box will allow resizing of all selected panes if (SelectedPanes.Count > 0) DrawSelectionBox(SelectedPanes, true); if (UseOrtho) GL.PopMatrix(); GL.UseProgram(0); glControl1.SwapBuffers(); } private static CustomRectangle GetSelectedPanesBounding(List panes) { CustomRectangle rect = new CustomRectangle(0, 0, 0, 0); List points = new List(); foreach (var pane in panes) { var paneRect = pane.CreateRectangle(); rect = paneRect.GetTransformedRectangle(pane.Parent, pane.GetTranslation(), pane.GetRotation(), pane.GetScale()); points.AddRange(new Vector2[4] { rect.TopLeftPoint, rect.TopRightPoint, rect.BottomRightPoint, rect.BottomLeftPoint, }); var minX = (int)points.Min(p => p.X); var maxX = (int)points.Max(p => p.X); var minY = (int)points.Min(p => p.Y); var maxY = (int)points.Max(p => p.Y); rect = new CustomRectangle(minX, maxX, maxY, minY); } points.Clear(); return rect; } public static void DrawSelectionBox(List panes, bool isSelected) { //Create a rectangle with the largest points selected CustomRectangle rect = GetSelectedPanesBounding(panes); GL.Disable(EnableCap.Blend); GL.Disable(EnableCap.AlphaTest); GL.Disable(EnableCap.Texture2D); GL.UseProgram(0); GL.Begin(PrimitiveType.LineLoop); GL.Color4(isSelected ? Color.Red : Color.Green); GL.Vertex2(rect.BottomLeftPoint); GL.Vertex2(rect.BottomRightPoint); GL.Vertex2(rect.TopRightPoint); GL.Vertex2(rect.TopLeftPoint); GL.End(); if (isSelected) { var transformed = rect; var leftTop = new Vector2(transformed.LeftPoint, transformed.TopPoint); var left = new Vector2(transformed.LeftPoint, (transformed.BottomPoint + transformed.TopPoint) / 2); var leftBottom = new Vector2(transformed.LeftPoint, transformed.BottomPoint); var rightTop = new Vector2(transformed.RightPoint, transformed.TopPoint); var right = new Vector2(transformed.RightPoint, (transformed.BottomPoint + transformed.TopPoint) / 2); var rightBottom = new Vector2(transformed.RightPoint, transformed.BottomPoint); var top = new Vector2((transformed.RightPoint + transformed.LeftPoint) / 2, transformed.TopPoint); var bottom = new Vector2((transformed.RightPoint + transformed.LeftPoint) / 2, transformed.BottomPoint); DrawEdgeSquare(leftTop); DrawEdgeSquare(left); DrawEdgeSquare(leftBottom); DrawEdgeSquare(rightTop); DrawEdgeSquare(right); DrawEdgeSquare(rightBottom); DrawEdgeSquare(top); DrawEdgeSquare(bottom); } GL.Enable(EnableCap.Blend); GL.Enable(EnableCap.AlphaTest); GL.Enable(EnableCap.Texture2D); } private static void DrawEdgeSquare(Vector2 position) { float scale = 5; GL.Begin(PrimitiveType.LineLoop); GL.Color4(Color.Red); GL.Vertex2(position.X + -1 * scale, position.Y + -1 * scale); GL.Vertex2(position.X + 1 * scale, position.Y + -1 * scale); GL.Vertex2(position.X + 1 * scale, position.Y + 1 * scale); GL.Vertex2(position.X + -1 * scale, position.Y + 1 * scale); GL.End(); } private void RenderPanes(BxlytShader shader, BasePane pane, bool isRoot, byte parentAlpha, bool parentAlphaInfluence, BasePane partPane = null, int stage = 0) { if (!pane.DisplayInEditor) return; GL.PushMatrix(); //Check XY rotation and draw the pane before it was rotated bool isRotatedXY = pane.Rotate.X != 0 || pane.Rotate.Y != 0; if (isRotatedXY && SelectedPanes.Contains(pane)) { GL.PushMatrix(); GL.Translate(pane.Translate.X, pane.Translate.Y, 0); GL.Rotate(pane.Rotate.Z, 0, 0, 1); GL.Scale(pane.Scale.X, pane.Scale.Y, 1); DrawDefaultPane(shader, pane, false); GL.PopMatrix(); } var translate = pane.GetTranslation(); var rotate = pane.GetRotation(); var scale = pane.GetScale(); if (partPane != null) { translate = translate + pane.Translate; scale = scale * pane.Scale; rotate = rotate + pane.Rotate; var prtPane = (Cafe.PRT1)partPane; scale = new Syroot.Maths.Vector2F( scale.X * prtPane.MagnifyX, scale.Y * prtPane.MagnifyY); } //Note rotation matrix done by shaders GL.Translate(translate.X, translate.Y, 0); GL.Scale(scale.X, scale.Y, 1); byte alpha = pane.Alpha; if (pane.animController.PaneVertexColors.ContainsKey(LVCTarget.PaneAlpha)) alpha = (byte)pane.animController.PaneVertexColors[LVCTarget.PaneAlpha]; byte effectiveAlpha = (byte)(parentAlpha == 255 ? alpha : (alpha * parentAlpha) / 255); if (!parentAlphaInfluence) effectiveAlpha = alpha; parentAlphaInfluence = parentAlphaInfluence || pane.InfluenceAlpha; if (!isRoot) { bool isSelected = SelectedPanes.Contains(pane); if (!pane.Visible && !pane.animController.Visibile) DrawDefaultPane(shader, pane, isSelected); else if (pane is IPicturePane) BxlytToGL.DrawPictureBox(pane, Camera, GameWindow, effectiveAlpha, Textures, isSelected); else if (pane is IWindowPane) BxlytToGL.DrawWindowPane(pane, Camera, GameWindow, effectiveAlpha, Textures, isSelected); else if (pane is IBoundryPane) { shader.Enable(); shader.SetBasic(pane, Color.White); BxlytToGL.DrawBoundryPane(pane, GameWindow, effectiveAlpha, isSelected); shader.Disable(); } else if (pane is ITextPane && Runtime.LayoutEditor.DisplayTextPane) { var textPane = (ITextPane)pane; Bitmap bitmap = null; foreach (var fontFile in FirstPlugin.PluginRuntime.BxfntFiles) { if (Utils.CompareNoExtension(fontFile.FileName, textPane.FontName)) bitmap = fontFile.GetBitmap(textPane.Text, false, pane); } if (bitmap != null) BxlytToGL.DrawTextbox(pane, Camera, GameWindow, bitmap, effectiveAlpha, Textures, SelectedPanes, textPane.RenderableFont == null, isSelected); else DrawDefaultPane(shader, pane, isSelected); } else if (pane is Cafe.SCR1) BxlytToGL.DrawScissorPane(pane, GameWindow, effectiveAlpha, isSelected); else if (pane is Cafe.ALI1) BxlytToGL.DrawAlignmentPane(pane, GameWindow, effectiveAlpha, isSelected); else if (pane is Cafe.PRT1) DrawPartsPane(shader, (Cafe.PRT1)pane, effectiveAlpha, isSelected, parentAlphaInfluence); else DrawDefaultPane(shader, pane, isSelected); } else isRoot = false; byte childAlpha = pane.InfluenceAlpha || parentAlphaInfluence ? effectiveAlpha : byte.MaxValue; foreach (var childPane in pane.Childern) RenderPanes(shader, childPane, isRoot, childAlpha, parentAlphaInfluence, partPane); GL.PopMatrix(); } private void DrawRootPane(BasePane pane) { Color color = Color.Black; if (SelectedPanes.Contains(pane)) color = Color.Red; CustomRectangle rect = pane.CreateRectangle(); //Draw a quad which is the backcolor but lighter GL.Begin(PrimitiveType.Quads); GL.Color3(BackgroundColor.Lighten(10)); GL.Vertex2(rect.TopLeftPoint); GL.Vertex2(rect.TopRightPoint); GL.Vertex2(rect.BottomRightPoint); GL.Vertex2(rect.BottomLeftPoint); GL.End(); //Draw outline of root pane GL.Begin(PrimitiveType.LineLoop); GL.PolygonOffset(0.5f, 2); GL.LineWidth(33); GL.Color3(color); GL.Vertex2(rect.TopLeftPoint); GL.Vertex2(rect.TopRightPoint); GL.Vertex2(rect.BottomRightPoint); GL.Vertex2(rect.BottomLeftPoint); GL.End(); } private void DrawDefaultPane(BxlytShader shader, BasePane pane, bool isSelectionBox = false) { if (!Runtime.LayoutEditor.DisplayNullPane || GameWindow || Runtime.LayoutEditor.IsGamePreview) return; shader.Enable(); shader.SetBasic(pane, Color.Black); Vector2[] TexCoords = new Vector2[] { new Vector2(1,1), new Vector2(0,1), new Vector2(0,0), new Vector2(1,0) }; Color color = Color.Black; if (SelectedPanes.Contains(pane)) color = Color.Red; Color[] Colors = new Color[] { color, color, color, color, }; BxlytToGL.DrawRectangle(pane, GameWindow, pane.Rectangle, TexCoords, Colors, true, 255, isSelectionBox); shader.Disable(); } private void DrawPartsPane(BxlytShader shader, Cafe.PRT1 pane, byte effectiveAlpha, bool isSelected, bool parentInfluenceAlpha) { if (Runtime.LayoutEditor.PartsAsNullPanes) { DrawDefaultPane(shader, pane, isSelected); return; } pane.UpdateTextureData(this.Textures); var partPane = pane.GetExternalPane(); if (partPane != null) RenderPanes(shader, partPane, true, effectiveAlpha, parentInfluenceAlpha); else DrawDefaultPane(shader, pane, isSelected); if (pane.Properties != null) { foreach (var prop in pane.Properties) { if (prop.Property != null) { RenderPanes(shader, prop.Property, false, effectiveAlpha, parentInfluenceAlpha || pane.InfluenceAlpha); } } } } private void DrawBackground() { if (backgroundTex == null) { /* backgroundTex = RenderableTex.FromBitmap(Properties.Resources.GridBackground); backgroundTex.TextureWrapR = TextureWrapMode.Repeat; backgroundTex.TextureWrapT = TextureWrapMode.Repeat; GL.Enable(EnableCap.Texture2D); GL.BindTexture(TextureTarget.Texture2D, backgroundTex.TexID); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (float)backgroundTex.TextureWrapR); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (float)backgroundTex.TextureWrapT); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (float)backgroundTex.TextureMagFilter); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (float)backgroundTex.TextureMinFilter); float UVscale = 15; int PanelWidth = 9000; int PanelWHeight = 9000; Vector2 scaleCenter = new Vector2(0.5f, 0.5f); Vector2[] TexCoords = new Vector2[] { new Vector2(1,1), new Vector2(0,1), new Vector2(0,0), new Vector2(1,0), }; for (int i = 0; i < TexCoords.Length; i++) TexCoords[i] = (TexCoords[i] - scaleCenter) * 20 + scaleCenter; GL.MatrixMode(MatrixMode.Modelview); GL.LoadIdentity(); GL.PushMatrix(); GL.Scale(1, 1, 1); GL.Translate(0, 0, 0); GL.Color4(Color.White); GL.Begin(PrimitiveType.Quads); GL.TexCoord2(TexCoords[0]); GL.Vertex3(PanelWidth, PanelWHeight, 0); GL.TexCoord2(TexCoords[1]); GL.Vertex3(-PanelWidth, PanelWHeight, 0); GL.TexCoord2(TexCoords[2]); GL.Vertex3(-PanelWidth, -PanelWHeight, 0); GL.TexCoord2(TexCoords[3]); GL.Vertex3(PanelWidth, -PanelWHeight, 0); GL.End(); GL.BindTexture(TextureTarget.Texture2D, 0); GL.PopMatrix();*/ } } public void UpdateBackgroundColor(Color color) { Runtime.LayoutEditor.BackgroundColor = color; glControl1.Invalidate(); Config.Save(); } private void DrawXyLines() { if (GameWindow || Runtime.LayoutEditor.IsGamePreview) return; int lineLength = 20; GL.Color3(Color.Green); GL.Begin(PrimitiveType.Lines); GL.Vertex2(0, 0); GL.Vertex2(0, lineLength); GL.End(); GL.Color3(Color.Red); GL.Begin(PrimitiveType.Lines); GL.Vertex2(0, 0); GL.Vertex2(lineLength, 0); GL.End(); } private void DrawGrid() { if (!Runtime.LayoutEditor.DisplayGrid) return; var size = 40; var amount = 300; GL.LineWidth(0.001f); GL.Color3(BackgroundColor.Darken(20)); GL.Begin(PrimitiveType.Lines); int squareGridCounter = 0; for (var i = -amount; i <= amount; i++) { if (squareGridCounter > 5) { squareGridCounter = 0; GL.LineWidth(33f); } else { GL.LineWidth(0.001f); } GL.Vertex2(new Vector2(-amount * size, i * size)); GL.Vertex2(new Vector2(amount * size, i * size)); GL.Vertex2(new Vector2(i * size, -amount * size)); GL.Vertex2(new Vector2(i * size, amount * size)); squareGridCounter++; } GL.End(); GL.Color3(Color.Transparent); GL.PopAttrib(); } private bool mouseCameraDown = false; private bool mouseDown = false; private List SelectionBoxPanes = new List(); private bool showSelectionBox = false; private bool isPicked = false; private bool mouseMoving = false; private Point originMouse; private Point pickOriginMouse; private Point pickMouse; private Vector2 pickDistance; private PickAction pickAction = PickAction.None; private PickAxis pickAxis = PickAxis.All; private bool snapToGrid = false; private void glControl1_MouseDown(object sender, MouseEventArgs e) { if (GameWindow) return; pickAction = PickAction.None; pickAxis = PickAxis.All; if (Control.ModifierKeys == Keys.Shift && e.Button == MouseButtons.Left || e.Button == MouseButtons.Middle) { originMouse = e.Location; mouseCameraDown = true; glControl1.Invalidate(); } //Pick an object for moving else if (e.Button == MouseButtons.Left) { mouseDown = true; RenderEditor(); var coords = OpenGLHelper.convertScreenToWorldCoords(e.Location.X, e.Location.Y); GL.PopMatrix(); bool hasEdgeHit = false; var rect = GetSelectedPanesBounding(SelectedPanes); var edgePick = SearchEdgePicking(rect, coords.X, coords.Y); if (edgePick != PickAction.None) { pickAction = edgePick; isPicked = true; hasEdgeHit = true; } if (hasEdgeHit) { UndoManger.AddToUndo(new LayoutUndoManager.UndoActionTransform(SelectedPanes)); pickOriginMouse = e.Location; return; } BasePane hitPane = null; SearchHit(LayoutFile.RootPane, coords.X, coords.Y, ref hitPane); if (hitPane != null) { pickAction = PickAction.Translate; if (!SelectedPanes.Contains(hitPane)) { if (Control.ModifierKeys != Keys.Control) SelectedPanes.Clear(); SelectedPanes.Add(hitPane); } var paneRect = GetSelectedPanesBounding(SelectedPanes); var paneEdgePick = SearchEdgePicking(paneRect, coords.X, coords.Y); if (paneEdgePick != PickAction.None) pickAction = paneEdgePick; UndoManger.AddToUndo(new LayoutUndoManager.UndoActionTransform(SelectedPanes)); ParentEditor.UpdateUndo(); ParentEditor.UpdateHiearchyNodeSelection(hitPane); isPicked = true; } //Check control key (multi selecting panes) else if (Control.ModifierKeys != Keys.Control) SelectedPanes.Clear(); pickOriginMouse = e.Location; } else if (e.Button == MouseButtons.Right) { RenderEditor(); var coords = OpenGLHelper.convertScreenToWorldCoords(e.Location.X, e.Location.Y); pickOriginMouse = coords; GL.PopMatrix(); //Add a content menu var selectOverlapping = new STToolStripItem("Select Overlapping"); var createPanes = new STToolStripItem("Create Pane"); createPanes.DropDownItems.Add(new STToolStripItem("Null Pane", CreateNullPaneAction)); createPanes.DropDownItems.Add(new STToolStripItem("Picture Pane", CreatePicturePaneAction)); createPanes.DropDownItems.Add(new STToolStripItem("Part Pane", CreatePartPaneAction)); createPanes.DropDownItems.Add(new STToolStripItem("Text Box Pane", CreateTextPaneAction)); createPanes.DropDownItems.Add(new STToolStripItem("Window Pane", CreateWindowPaneAction)); createPanes.DropDownItems.Add(new STToolStripItem("Boundry Pane", CreateBoundryPaneAction)); var hitPanes = GetHitPanes(LayoutFile.RootPane, coords.X, coords.Y, new List()); for (int i = 0; i < hitPanes.Count; i++) selectOverlapping.DropDownItems.Add( new STToolStripItem(hitPanes[i].Name, SelectOverlappingAction)); stContextMenuStrip1.Items.Clear(); stContextMenuStrip1.Items.Add(createPanes); stContextMenuStrip1.Items.Add(selectOverlapping); stContextMenuStrip1.Items.Add(new STToolStripItem("Show All Hidden Panes", ShowAllPaneAction)); stContextMenuStrip1.Items.Add(new STToolStripItem("Paste (Experimental)", PastePaneAction) { Enabled = CopiedPanes.Count > 0 }); if (SelectedPanes.Count > 0) { stContextMenuStrip1.Items.Add(new STToolStripSeparator()); stContextMenuStrip1.Items.Add(new STToolStripItem("Copy (Experimental)", CopyPaneAction)); stContextMenuStrip1.Items.Add(new STToolStripItem("Edit Group")); stContextMenuStrip1.Items.Add(new STToolStripItem("Delete Selected Panes", DeletePaneAction)); stContextMenuStrip1.Items.Add(new STToolStripItem("Hide Selected Panes", HidePaneAction)); } stContextMenuStrip1.Show(Cursor.Position); } Console.WriteLine("SelectedPanes " + SelectedPanes.Count); } private void CreatePartPaneAction(object sender, EventArgs e) { var pane = ParentEditor.AddNewPartPane(); SetupNewPane(pane, pickOriginMouse); } private void CreateNullPaneAction(object sender, EventArgs e) { var pane = ParentEditor.AddNewNullPane(); SetupNewPane(pane, pickOriginMouse); } private void CreatePicturePaneAction(object sender, EventArgs e) { var pane = ParentEditor.AddNewPicturePane(); SetupNewPane(pane, pickOriginMouse); } private void CreateWindowPaneAction(object sender, EventArgs e) { var pane = ParentEditor.AddNewWindowPane(); SetupNewPane(pane, pickOriginMouse); } private void CreateTextPaneAction(object sender, EventArgs e) { var pane = ParentEditor.AddNewTextPane(); SetupNewPane(pane, pickOriginMouse); if (pane is Cafe.TXT1) ((Cafe.TXT1)pane).UpdateTextRender(); } private void CreateBoundryPaneAction(object sender, EventArgs e) { var pane = ParentEditor.AddNewBoundryPane(); SetupNewPane(pane, pickOriginMouse); } private void CopyPaneAction(object sender, EventArgs e) { CopyPanes(); } private void PastePaneAction(object sender, EventArgs e) { PastePanes(); } private void CopyPanes() { CopiedPanes.Clear(); foreach (var pane in SelectedPanes) { BasePane copiedPane = (BasePane)pane.Clone(); CopiedPanes.Add(copiedPane); } } private void PastePanes() { SelectedPanes.Clear(); foreach (var copiedPane in CopiedPanes) { //Copy again as the user may paste the same one multiple times //This will make sure it's a completely new instance BasePane pane = (BasePane)copiedPane.Clone(); ParentEditor.AddNewPastedPane(pane); SelectedPanes.Add(pane); } glControl1.Invalidate(); } private void SetupNewPane(BasePane pane, Point point) { if (pane == null) return; SelectedPanes.Clear(); pane.Translate = new Syroot.Maths.Vector3F(point.X, point.Y, 0); ParentEditor.LoadPaneEditorOnSelect(pane); glControl1.Invalidate(); } private void DeletePaneAction(object sender, EventArgs e) { DeleteSelectedPanes(); } private void HidePaneAction(object sender, EventArgs e) { HideSelectedPanes(); } private void ShowAllPaneAction(object sender, EventArgs e) { ShowHiddenPanes(); } private void HideSelectedPanes() { UndoManger.AddToUndo(new LayoutUndoManager.UndoActionPaneHide(SelectedPanes)); ParentEditor?.UpdateHiearchyTree(); SelectedPanes.Clear(); glControl1.Invalidate(); } private void ShowHiddenPanes() { UndoManger.AddToUndo(new LayoutUndoManager.UndoActionPaneHide(LayoutFile.PaneLookup.Values.ToList(), false)); ParentEditor?.UpdateHiearchyTree(); glControl1.Invalidate(); } private void DeleteSelectedPanes() { if (SelectedPanes.Count == 0) return; //Make sure to fill all the children in selected panes! for (int i = 0; i < SelectedPanes.Count; i++) SelectedPanes.AddRange(GetChildren(SelectedPanes, new List(), SelectedPanes[i])); UndoManger.AddToUndo(new LayoutUndoManager.UndoActionPaneDelete(SelectedPanes, LayoutFile)); LayoutFile.RemovePanes(SelectedPanes, LayoutFile.RootPane); SelectedPanes.Clear(); ParentEditor?.UpdateHiearchyTree(); glControl1.Invalidate(); } private List GetChildren(List selectedPanes, List childrenPanes, BasePane parent) { if (!selectedPanes.Contains(parent)) childrenPanes.Add(parent); foreach (var child in parent.Childern) GetChildren(selectedPanes, childrenPanes, child); return childrenPanes; } private void SelectOverlappingAction(object sender, EventArgs e) { var toolMenu = sender as STToolStripItem; if (toolMenu != null) { string name = toolMenu.Text; if (Control.ModifierKeys != Keys.Control) SelectedPanes.Clear(); if (LayoutFile.PaneLookup.ContainsKey(name)) SelectedPanes.Add(LayoutFile.PaneLookup[name]); glControl1.Invalidate(); } } private void SearchHit(BasePane pane, int X, int Y, ref BasePane SelectedPane) { bool isVisible = true; if (!Runtime.LayoutEditor.DisplayPicturePane && pane is IPicturePane) isVisible = false; if (!Runtime.LayoutEditor.DisplayWindowPane && pane is IWindowPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayBoundryPane && pane is IBoundryPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayTextPane && pane is ITextPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayNullPane && pane.IsNullPane) isVisible = false; if (isVisible && pane.DisplayInEditor && pane.IsHit(X, Y) && !pane.IsRoot) { //Select the first possible pane //If the pane is selected already, pick that instead //This is useful if the selected pane wants to be moved already if (SelectedPane == null || SelectedPanes.Contains(pane)) SelectedPane = pane; } //Keep searching even if we found our pane so we can find any that's selected foreach (var childPane in pane.Childern) SearchHit(childPane, X, Y, ref SelectedPane); } private List GetHitPanes(BasePane pane, CustomRectangle rect, List SelectedPanes) { bool isVisible = pane.Visible; if (!Runtime.LayoutEditor.DisplayPicturePane && pane is IPicturePane) isVisible = false; if (!Runtime.LayoutEditor.DisplayWindowPane && pane is IWindowPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayBoundryPane && pane is IBoundryPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayTextPane && pane is ITextPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayNullPane && pane.IsNullPane) isVisible = false; if (isVisible && pane.DisplayInEditor && pane.IsHit(rect) && pane.Name != "RootPane") if (!SelectedPanes.Contains(pane)) SelectedPanes.Add(pane); foreach (var childPane in pane.Childern) GetHitPanes(childPane, rect, SelectedPanes); return SelectedPanes; } private List GetHitPanes(BasePane pane, int X, int Y, List SelectedPanes) { bool isVisible = pane.Visible; if (!Runtime.LayoutEditor.DisplayPicturePane && pane is IPicturePane) isVisible = false; if (!Runtime.LayoutEditor.DisplayWindowPane && pane is IWindowPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayBoundryPane && pane is IBoundryPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayTextPane && pane is ITextPane) isVisible = false; if (!Runtime.LayoutEditor.DisplayNullPane && pane.IsNullPane) isVisible = false; if (isVisible && pane.DisplayInEditor && pane.IsHit(X, Y) && pane.Name != "RootPane") if (!SelectedPanes.Contains(pane)) SelectedPanes.Add(pane); foreach (var childPane in pane.Childern) GetHitPanes(childPane, X, Y, SelectedPanes); return SelectedPanes; } private PickAction SearchEdgePicking(CustomRectangle transformed, int X, int Y) { var leftTop = new Point(transformed.LeftPoint, transformed.TopPoint); var left = new Point(transformed.LeftPoint, (transformed.BottomPoint + transformed.TopPoint) / 2); var leftBottom = new Point(transformed.LeftPoint, transformed.BottomPoint); var rightTop = new Point(transformed.RightPoint, transformed.TopPoint); var right = new Point(transformed.RightPoint, (transformed.BottomPoint + transformed.TopPoint) / 2); var rightBottom = new Point(transformed.RightPoint, transformed.BottomPoint); var top = new Point((transformed.RightPoint + transformed.LeftPoint) / 2, transformed.TopPoint); var bottom = new Point((transformed.RightPoint + transformed.LeftPoint) / 2, transformed.BottomPoint); if (IsEdgeHit(leftTop, X, Y)) return PickAction.DragTopLeft; else if (IsEdgeHit(left, X, Y)) return PickAction.DragLeft; else if (IsEdgeHit(leftBottom, X, Y)) return PickAction.DragBottomLeft; else if (IsEdgeHit(rightTop, X, Y)) return PickAction.DragTopRight; else if (IsEdgeHit(rightBottom, X, Y)) return PickAction.DragBottomRight; else if (IsEdgeHit(right, X, Y)) return PickAction.DragRight; else if (IsEdgeHit(top, X, Y)) return PickAction.DragTop; else if (IsEdgeHit(bottom, X, Y)) return PickAction.DragBottom; return PickAction.None; } private bool IsEdgeHit(Point point, int X, int Y, int size = 10) { if ((X > point.X - size) && (X < point.X + size) && (Y > point.Y - size) && (Y < point.Y + size)) return true; else return false; } private void glControl1_MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Middle) { pickAxis = PickAxis.All; mouseCameraDown = false; mouseDown = false; isPicked = false; mouseMoving = false; showSelectionBox = false; foreach (var pane in SelectionBoxPanes) if (!SelectedPanes.Contains(pane)) SelectedPanes.Add(pane); SelectionBoxPanes.Clear(); ParentEditor.RefreshEditors(); if (SelectedPanes.Count > 0) ParentEditor.UpdateHiearchyNodeSelection(SelectedPanes.FirstOrDefault()); } glControl1.Invalidate(); } public enum PickAction { None, DragTopRight, DragTopLeft, DragTop, DragLeft, DragRight, DragBottom, DragBottomLeft, DragBottomRight, Translate, Scale, Rotate } public enum PickAxis { All, X, Y, Z, } private void glControl1_MouseMove(object sender, MouseEventArgs e) { if (GameWindow) return; if (UseOrtho) GL.PopMatrix(); if (SelectedPanes.Count > 0 && !showSelectionBox) { RenderEditor(); var posWorld = OpenGLHelper.convertScreenToWorldCoords(e.Location.X, e.Location.Y); GL.PopMatrix(); //Setup edge picking with move event bool hasPick = false; var paneRect = GetSelectedPanesBounding(SelectedPanes); var pickState = SearchEdgePicking(paneRect, posWorld.X, posWorld.Y); if (pickState != PickAction.None) { if (pickState == PickAction.DragTop) Cursor.Current = Cursors.SizeNS; if (pickState == PickAction.DragBottom) Cursor.Current = Cursors.SizeNS; if (pickState == PickAction.DragLeft) Cursor.Current = Cursors.SizeWE; if (pickState == PickAction.DragRight) Cursor.Current = Cursors.SizeWE; if (pickState == PickAction.DragBottomLeft) Cursor.Current = Cursors.SizeNESW; if (pickState == PickAction.DragBottomRight) Cursor.Current = Cursors.SizeNWSE; if (pickState == PickAction.DragTopLeft) Cursor.Current = Cursors.SizeNWSE; if (pickState == PickAction.DragTopRight) Cursor.Current = Cursors.SizeNESW; hasPick = true; } else if (isPicked && pickAction != PickAction.None) { if (pickAction == PickAction.Translate) Cursor.Current = Cursors.SizeAll; if (pickAction == PickAction.Rotate) Cursor.Current = Cursors.SizeAll; if (pickAction == PickAction.Scale) Cursor.Current = Cursors.SizeAll; hasPick = true; } if (!hasPick) Cursor.Current = Cursors.Default; } if (isPicked && !showSelectionBox) { RenderEditor(); var temp = e.Location; var curPos = OpenGLHelper.convertScreenToWorldCoords(temp.X, temp.Y); var prevPos = OpenGLHelper.convertScreenToWorldCoords(pickOriginMouse.X, pickOriginMouse.Y); var pickMouse = new Point((int)(prevPos.X - curPos.X), (int)(prevPos.Y - curPos.Y)); if (pickAction == PickAction.Translate) { foreach (var pane in SelectedPanes) { if (pickOriginMouse != Point.Empty) { float posX = pane.Translate.X; float posY = pane.Translate.Y; float posZ = pane.Translate.Z; if (pickAxis == PickAxis.X) posX = pane.Translate.X - pickMouse.X; if (pickAxis == PickAxis.Y) posY = pane.Translate.Y - pickMouse.Y; if (pickAxis == PickAxis.All) { posX = pane.Translate.X - pickMouse.X; posY = pane.Translate.Y - pickMouse.Y; } if (Runtime.LayoutEditor.AnimationEditMode) { } else { if (!Runtime.LayoutEditor.TransformChidlren) pane.KeepChildrenTransform(posX, posY); if (snapToGrid) { int gridCubeWidth = 16, gridCubeHeight = 16; pane.Translate = new Syroot.Maths.Vector3F( (float)(Math.Round(posX / gridCubeWidth) * gridCubeWidth), (float)(Math.Round(posY / gridCubeHeight) * gridCubeHeight), posZ); } else { pane.Translate = new Syroot.Maths.Vector3F(posX, posY, posZ); } } } } } if (pickAction == PickAction.Scale) { foreach (var pane in SelectedPanes) { if (pickOriginMouse != Point.Empty) { float scaX = pane.Scale.X; float scaY = pane.Scale.Y; if (pickAxis == PickAxis.X) scaX = pane.Scale.X - pickMouse.X; if (pickAxis == PickAxis.Y) scaY = pane.Scale.Y - pickMouse.Y; if (pickAxis == PickAxis.All) { scaX = pane.Scale.X - pickMouse.X; scaY = pane.Scale.Y - pickMouse.Y; } if (Runtime.LayoutEditor.AnimationEditMode) { } else { if (snapToGrid) { int gridCubeWidth = 16, gridCubeHeight = 16; pane.Scale = new Syroot.Maths.Vector2F( (float)(Math.Round(scaX / gridCubeWidth) * gridCubeWidth), (float)(Math.Round(scaY / gridCubeHeight) * gridCubeHeight)); } else { pane.Scale = new Syroot.Maths.Vector2F(scaX, scaY); } } } } } else if (!showSelectionBox) { var selectionBox = GetSelectedPanesBounding(SelectedPanes); //Setup edge picking with move event foreach (var pane in SelectedPanes) pane.TransformRectangle(pickAction, selectionBox, pickMouse.X, pickMouse.Y); } pickOriginMouse = temp; RenderScene(); } if (mouseDown && !isPicked) { RenderEditor(); var temp = e.Location; var curPos = OpenGLHelper.convertScreenToWorldCoords(temp.X, temp.Y); var prevPos = OpenGLHelper.convertScreenToWorldCoords(pickOriginMouse.X, pickOriginMouse.Y); DrawSelectionBox(prevPos, curPos); } if (mouseCameraDown) { var pos = new Vector2(e.Location.X - originMouse.X, e.Location.Y - originMouse.Y); Camera.Position.X += pos.X; Camera.Position.Y -= pos.Y; originMouse = e.Location; glControl1.Invalidate(); } } private CustomRectangle SelectionBox; private void DrawSelectionBox(Point point1, Point point2) { SelectionBoxPanes.Clear(); int left = point1.X; int right = point2.X; int top = point1.Y; int bottom = point2.Y; //Determine each point direction to see what is left/right/top/bottom if (bottom > top) { top = point2.Y; bottom = point1.Y; } if (left > right) { right = point1.X; left = point2.X; } showSelectionBox = true; SelectionBox = new CustomRectangle(left, right, top, bottom); var hitPanes = GetHitPanes(LayoutFile.RootPane, SelectionBox, new List()); foreach (var pane in hitPanes) if (!SelectionBoxPanes.Contains(pane)) SelectionBoxPanes.Add(pane); RenderScene(true); } protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); if (UseOrtho) { if (e.Delta > 0 && Camera.Zoom > 0) Camera.Zoom += 0.1f; if (e.Delta < 0 && Camera.Zoom < 100 && Camera.Zoom > 0.1) Camera.Zoom -= 0.1f; } else { if (e.Delta > 0 && Camera.Zoom > 0.1) Camera.Zoom -= 0.1f; if (e.Delta < 0 && Camera.Zoom < 100 && Camera.Zoom > 0) Camera.Zoom += 0.1f; } glControl1.Invalidate(); } private void glControl1_Resize(object sender, EventArgs e) { glControl1.Invalidate(); } private void glControl1_KeyDown(object sender, KeyEventArgs e) { if (isPicked && e.KeyCode == Keys.X) { pickAxis = PickAxis.X; glControl1.Invalidate(); } if (isPicked && e.KeyCode == Keys.Y) { pickAxis = PickAxis.Y; glControl1.Invalidate(); } else if (e.Control && e.KeyCode == Keys.Z) // Ctrl + Z undo { UndoManger.Undo(); ParentEditor.UpdateUndo(); glControl1.Invalidate(); } else if (e.Control && e.KeyCode == Keys.R) // Ctrl + R redo { UndoManger.Redo(); ParentEditor.UpdateUndo(); glControl1.Invalidate(); } else if (e.Control && e.KeyCode == Keys.C) // Ctrl + C copy { CopyPanes(); glControl1.Invalidate(); } else if (e.Control && e.KeyCode == Keys.V) // Ctrl + V paste { PastePanes(); glControl1.Invalidate(); } else if (e.Control && e.KeyCode == Keys.A) // Ctrl + A select all { SelectedPanes.Clear(); foreach (var pane in LayoutFile.PaneLookup.Values) if (!pane.IsRoot) SelectedPanes.Add(pane); glControl1.Invalidate(); } else if (e.KeyCode == Keys.Delete) { DeleteSelectedPanes(); } } private void glControl1_DragEnter(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(typeof(ListViewItem))) { e.Effect = DragDropEffects.Move; } } private void glControl1_DragDrop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(typeof(ListViewItem))) { var item = e.Data.GetData(typeof(ListViewItem)) as ListViewItem; string texture = item.Text; ParentEditor.DeselectTextureList(); if (Textures.ContainsKey(texture)) { var point = this.PointToClient(new Point(e.X, e.Y)); RenderEditor(); var coords = OpenGLHelper.convertScreenToWorldCoords(point.X, point.Y); GL.PopMatrix(); var pane = ParentEditor.AddNewPicturePane(); pane.Width = Textures[texture].Width; pane.Height = Textures[texture].Height; ((IPicturePane)pane).Material.AddTexture(texture); SetupNewPane(pane, coords); this.Focus(); } } } } }