using System; using System.CodeDom; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using FizzyLauncher; using FizzyLauncher.Windows.Forms; namespace RyzStudio.Windows.Forms { public partial class TileGridPanelLayout : RyzStudio.Windows.Forms.TUserControl where T : Control { public static int CalcWidth(int tileCount) => (tileCount * tileSize) + ((tileCount - 1) * margin); public class GridTileItem { public T Tile { get; set; } public Point Coord { get; set; } = new Point(0, 0); } protected const int tileSize = 70; protected const int margin = 3; protected const int labelHeight = 20; protected const int collapseIncrement = 6; protected const int expandIncrement = 8; protected int collapseHeight = 0; protected int expandedHeight = 0; protected bool isAnimating = false; protected bool isExpanded = true; protected Point lastMousePosition = new Point(); protected Point gridSize = new Point(0, 0); public TileGridPanelLayout() : base() { InitializeComponent(); this.AllowDrop = true; this.BackColor = Color.Transparent; this.Margin = new Padding(0); this.Padding = new Padding(0, 0, 0, 10); } protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded(e); e.Control.MouseMove += (sender, e) => { this.MoveTile(sender as T, e.X, e.Y); }; } private void tilePanel_Move(object sender, EventArgs e) { throw new NotImplementedException(); } protected override void OnDragOver(DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Link; } else { e.Effect = DragDropEffects.None; } } protected override void OnMouseClick(MouseEventArgs e) { base.OnMouseClick(e); lastMousePosition = e.Location; bool isLabel = ((e.Location.X >= 0) && (e.Location.X <= this.Width) && (e.Location.Y >= 0) && (e.Location.Y <= 20)); if (e.Button == MouseButtons.Left) { // do nothing } else if (e.Button == MouseButtons.Right) { if (isLabel) { this.TitleContextMenuStrip?.Show(this, e.Location); } else { this.ContainerContextMenuStrip?.Show(this, e.Location); } } } protected override void OnMouseDoubleClick(MouseEventArgs e) => base.OnMouseClick(e); protected override async void OnResize(EventArgs e) { base.OnResize(e); await this.Invalidate(); } protected virtual async void label1_MouseClick(object sender, MouseEventArgs e) { if (isAnimating) { return; } if (e.Button == MouseButtons.Left) { this.IsExpanded = !this.IsExpanded; //label1.Image = (this.IsExpanded ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16); await this.Invalidate(); // exclusivity //if (this.IsExpanded) //{ //if (this.Model.IsExclusive) //{ // if (this.FlowLayoutPanel != null) // { // foreach (TilePanelLayout item in this.FlowLayoutPanel.Controls.OfType()) // { // if (item.Equals(this)) // { // continue; // } // await item.Collapse(); // } // } //} //} //label1.Image = (this.IsExpanded ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16); } else if (e.Button == MouseButtons.Right) { this.TitleContextMenuStrip?.Show(this, e.Location); } } #region encapsulation [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Padding Margin { get => base.Margin; set => base.Margin = new Padding(0); } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new Padding Padding { get => base.Padding; set => base.Padding = new Padding(0, 0, 0, 10); } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public new ContextMenuStrip ContextMenuStrip { get; set; } = null; #endregion [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Point GridSize { get => gridSize; protected set { gridSize = value; expandedHeight = (this.TileSize * value.Y) + labelHeight; int newWidth = CalcWidth(gridSize.X); this.Size = new Size(newWidth, (this.IsExpanded ? this.ExpandedHeight : this.CollapseHeight)); } } [Category("Appearance")] public ContextMenuStrip TitleContextMenuStrip { get; set; } = null; [Category("Appearance")] public ContextMenuStrip ContainerContextMenuStrip { get; set; } = null; [Category("Appearance")] public bool EnableAnimation { get; set; } = false; [Category("Appearance")] public string Title { get => label1.Text?.Trim(); set => label1.Text = " " + value; } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public List GridTileItems { get; protected set; } = new List(); [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public FlowLayoutPanel FlowLayoutPanel { get => UIControl.GetParentsUntil(this.Parent); } protected int CollapseHeight => labelHeight + collapseHeight; protected int ExpandedHeight => expandedHeight + this.Padding.Bottom; protected int TileSize => (tileSize + margin); [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsExpanded { get => isExpanded; protected set { isExpanded = value; //label1.Image = (isExpanded ? UIResource1.toggle_right_16 : UIResource1.toggle_left_16); label1.Image = (isExpanded ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16); } } public void Clear() { this.Controls.Clear(); } public async Task Collapse() { await Task.Run(() => { if (isAnimating) return; isAnimating = true; this.IsExpanded = false; if (this.EnableAnimation) { while (this.Height > this.CollapseHeight) { UIControl.SetHeight(this, (this.Height - collapseIncrement)); Thread.Sleep(10); } } UIControl.SetHeight(this, this.CollapseHeight); isAnimating = false; this.Invalidate(this.DisplayRectangle, false); }); } public async Task Expand() { await Task.Run(() => { if (isAnimating) return; isAnimating = true; this.IsExpanded = true; if (this.EnableAnimation) { while (this.Height < this.ExpandedHeight) { UIControl.SetHeight(this, (this.Height + expandIncrement)); Thread.Sleep(10); } } UIControl.SetHeight(this, this.ExpandedHeight); isAnimating = false; this.Invalidate(this.DisplayRectangle, false); }); } public new async Task Invalidate() { base.Invalidate(); if (isAnimating) { return; } if (this.IsExpanded) { await this.Expand(); } else { await this.Collapse(); } } public void MoveTop() { if (this.FlowLayoutPanel != null) { this.FlowLayoutPanel.Controls.SetChildIndex(this, 0); } } public void MoveUp() { if (this.FlowLayoutPanel != null) { int pos = this.FlowLayoutPanel.Controls.GetChildIndex(this); if (pos <= 0) { return; } this.FlowLayoutPanel.Controls.SetChildIndex(this, (pos - 1)); } } public void MoveDown() { if (this.FlowLayoutPanel != null) { int pos = this.FlowLayoutPanel.Controls.GetChildIndex(this); if (pos >= (this.FlowLayoutPanel.Controls.Count - 1)) { return; } this.FlowLayoutPanel.Controls.SetChildIndex(this, (pos + 1)); } } public void MoveBottom() { if (this.FlowLayoutPanel != null) { this.FlowLayoutPanel.Controls.SetChildIndex(this, (this.FlowLayoutPanel.Controls.Count - 1)); } } public void MoveTile(T panel, int posX, int posY) { GridTileItem item = this.GridTileItems.Where(x => x.Tile.Equals(panel)).FirstOrDefault(); if (item == null) { return; } //Point newPosition = ConvertLocationToNearestCoord(posX, posY); //Point newPosition = ConvertLocationToCoord(posX, posY); Point newPosition = ConvertLocationToCoord(panel.Location.X, panel.Location.Y); if (!IsCoordInBounds(newPosition)) { return; } if (HasTile(newPosition)) { GridTileItem swapItem = this.GridTileItems.Where(x => x.Coord.Equals(newPosition)).FirstOrDefault(); if (swapItem != null) { swapItem.Coord = item.Coord; swapItem.Tile.Location = ConvertCoordToLocation(item.Coord); } item.Coord = newPosition; panel.Location = ConvertCoordToLocation(newPosition); } else { item.Coord = newPosition; panel.Location = ConvertCoordToLocation(newPosition); } } public void Remove() { if (this.FlowLayoutPanel != null) { this.FlowLayoutPanel.Controls.Remove(this); } } public void Remove(T panel) { GridTileItem m = this.GridTileItems.Where(x => x.Tile.Equals(panel)).FirstOrDefault(); if (m != null) { this.GridTileItems.Remove(m); } this.Controls.Remove(panel); } //protected Point CalcCoordFromPosition(int posX, int posY) //{ // int x = (int)Math.Round(decimal.Divide(posX, this.TileSize)); // int y = (int)Math.Round(decimal.Divide((posY - labelHeight), this.TileSize)); // if (x < 0) x = 0; // if (y < 0) y = 0; // return new Point((x * this.TileSize), ((y * this.TileSize) + labelHeight)); //} protected Point ConvertCoordToLocation(Point position) => new Point((position.X * this.TileSize), ((position.Y * this.TileSize) + labelHeight)); protected Point ConvertLocationToCoord(int posX, int posY) { int x = (int)Math.Round(decimal.Divide(posX, this.TileSize)); int y = (int)Math.Round(decimal.Divide((posY - labelHeight), this.TileSize)); //int x = (int)Math.Ceiling(decimal.Divide(posX, this.TileSize)); //int y = (int)Math.Ceiling(decimal.Divide((posY - labelHeight), this.TileSize)); //x--; //y--; if (x < 0) x = 0; if (y < 0) y = 0; return new Point(x, y); } //protected Point ConvertLocationToNearestCoord(int posX, int posY) //{ // int x = (int)Math.Round(decimal.Divide(posX, this.TileSize)); // int y = (int)Math.Round(decimal.Divide((posY - labelHeight), this.TileSize)); // if (x < 0) x = 0; // if (y < 0) y = 0; // return new Point(x, y); //} protected bool HasTile(Point position) { if (GridTileItems == null) { return false; } if (GridTileItems.Count <= 0) { return false; } return GridTileItems.Any(x => x.Coord.Equals(position)); } protected bool IsCoordInBounds(Point position) { if (position.X >= this.GridSize.X) { return false; } if (position.Y >= this.GridSize.Y) { return false; } return true; } protected Point? FindFirstFreeCoord() { for (int y = 0; y < this.GridSize.Y; y++) { for (int x = 0; x < this.GridSize.X; x++) { if (HasTile(new Point(x, y))) { continue; } return new Point(x, y); } } return null; } protected Point? FindLastFreeCoord() { Point gridSize = this.GridSize; // none available if (GridTileItems.Count >= (gridSize.X * gridSize.Y)) { return null; } if (GridTileItems.Count <= 0) { return FindFirstFreeCoord(); } // only one available if (GridTileItems.Count >= ((gridSize.X * gridSize.Y) - 1)) { return FindFirstFreeCoord(); } Point? rv = null; for (int y = (gridSize.Y - 1); y >= 0; y--) { for (int x = (gridSize.X - 1); x >= 0; x--) { if (HasTile(new Point(x, y))) { if (rv.HasValue) { return rv; } } else { rv = new Point(x, y); } } } return null; } protected Point? FindNextFreeCoord(Point coord) { Point newCoord = coord; while (true) { newCoord.X++; if (newCoord.X >= this.GridSize.X) { newCoord.Y++; newCoord.X = 0; } if (!IsCoordInBounds(newCoord)) { return null; } if (HasTile(newCoord)) { continue; } return newCoord; } } protected Point? ResolveCoord(Point coord) { if (!IsCoordInBounds(coord)) { return null; } if (!HasTile(coord)) { return coord; } return FindNextFreeCoord(coord); } } }