This repository has been archived on 2024-08-06. You can view files and clone it, but cannot push or open issues or pull requests.
linear-app-launcher/Windows/TileGridPanelLayout.cs

582 lines
17 KiB
C#
Raw Normal View History

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);
}
}
}