582 lines
17 KiB
C#
582 lines
17 KiB
C#
|
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<T> : 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<TilePanelLayout>())
|
|||
|
// {
|
|||
|
// 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<GridTileItem> GridTileItems { get; protected set; } = new List<GridTileItem>();
|
|||
|
|
|||
|
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|||
|
public FlowLayoutPanel FlowLayoutPanel { get => UIControl.GetParentsUntil<FlowLayoutPanel>(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);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|