using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using FizzyLauncher.Models; using RyzStudio.Windows.Forms; namespace FizzyLauncher.Windows.Forms { public partial class TilePanelLayout : RyzStudio.Windows.Forms.TC1UserControl { public static int CalcWidth(int tileCount) => (tileCount * tileSize) + ((tileCount - 1) * margin); public class Item { public TilePanel 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 TileGroupModel groupModel = null; protected List items = new List(); protected int collapseHeight = 0; protected int expandedHeight = 0; protected bool isAnimating = false; protected bool isChecked = true; protected Point lastMousePosition = new Point(); protected Point gridSize = new Point(); public TilePanelLayout(TileGroupModel model) : base() { InitializeComponent(); this.AllowDrop = true; this.BackColor = Color.Transparent; this.LoadModel(model); label1.Location = new Point(0, 4); label1.Margin = new Padding(0); label1.Padding = new Padding(0); } protected override void OnDragDrop(DragEventArgs e) { string[] fileList = e.Data.GetData(DataFormats.FileDrop) as string[]; this.DropFileList(fileList); } protected override void OnDragOver(DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Link; } else { e.Effect = DragDropEffects.None; } } protected override void OnLoad(EventArgs e) { base.OnLoad(e); this.Margin = new Padding(0); this.Padding = new Padding(0, 0, 0, 10); } protected override async void OnResize(EventArgs e) { base.OnResize(e); await this.InvalidateContainer(); } 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) { contextMenuStrip2.Show(this, e.Location); } else { contextMenuStrip1.Show(this, e.Location); } } } protected override void OnMouseDoubleClick(MouseEventArgs e) => base.OnMouseClick(e); public Point GridSize { get => gridSize; } public bool EnableAnimation { get { MainForm mainForm = this.MainForm; if (mainForm == null) { return false; } if (mainForm.CurrentSession == null) { return false; } return mainForm.CurrentSession.EnableAnimation; } } public int CollapseHeight => labelHeight + collapseHeight; public int ExpandedHeight => expandedHeight + this.Padding.Bottom; public void DropFileList(string[] fileList) { if (fileList == null) { return; } if (fileList.Length <= 0) { return; } if (string.IsNullOrWhiteSpace(fileList[0])) { return; } TileModel model = new TileModel() { ProcessFilename = fileList[0], Title = Path.GetFileName(fileList[0]) }; // exe if (Path.GetExtension(fileList[0]).Equals(".exe", StringComparison.CurrentCultureIgnoreCase)) { if (File.Exists(fileList[0])) { try { FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(fileList[0]); if (fvi != null) { model.Title = fvi.ProductName; } } catch { // do nothing } } if (string.IsNullOrWhiteSpace(model.Title)) { model.Title = Path.GetFileNameWithoutExtension(fileList[0]); } } this.AddTile(model); } public TileGroupModel Model { get { TileGroupModel rs = new TileGroupModel() { Title = groupModel.Title, GridSize = new Size(this.GridSize.X, this.GridSize.Y), IsExpanded = isChecked, IsExclusive = groupModel.IsExclusive, Items = this.Tiles }; return rs; } } public FlowLayoutPanel FlowLayoutPanel { get { Control parentControl = this.Parent; while (true) { if (parentControl == null) { return null; } if (parentControl.GetType() == typeof(FlowLayoutPanel)) { return parentControl as FlowLayoutPanel; } parentControl = parentControl.Parent; } } } public MainForm MainForm { get { Control parentControl = this.FlowLayoutPanel; while (true) { if (parentControl == null) { return null; } if (parentControl.GetType() == typeof(MainForm)) { return parentControl as MainForm; } parentControl = parentControl.Parent; } } } public List Tiles { get { List rs = new List(); foreach (Item item in items) { TileModel model = item.Tile.ModelInfo; model.Position = item.Coord; rs.Add(model); } return rs; } } public int TileSize => (tileSize + margin); public void AddTile(TileModel tile) { Point gridSize = this.GridSize; if (items.Count >= (gridSize.X * gridSize.Y)) { this.SetGridSize(gridSize.X, (gridSize.Y + 1)); } Point? newCoord = tile.Position; if ((newCoord == null) || hasTile(tile.Position)) { newCoord = findLastFreeCoord(); } if (newCoord == null) { return; } tile.Position = newCoord.Value; TilePanel panel = new TilePanel(); panel.LoadInfo(tile); panel.Location = convertCoordToLocation(tile.Position); items.Add(new Item() { Tile = panel, Coord = tile.Position }); this.Controls.Add(panel); } public void Clear() { this.Controls.Clear(); } public async Task Collapse() { await Task.Run(() => { if (isAnimating) return; isAnimating = true; isChecked = 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; isChecked = 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 Point GetTilePosition(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)); } public async Task InvalidateContainer() { if (isAnimating) { return; } if (isChecked) { await this.Expand(); } else { await this.Collapse(); } } public void AddGroup() { if (this.FlowLayoutPanel == null) { return; } this.FlowLayoutPanel.Controls.Add(new TilePanelLayout(new TileGroupModel() { Title = "New Group", GridSize = new Size(8, 1) })); } public void AddRow() => this.SetGridSize(gridSize.X, (gridSize.Y + 1)); public void EditGroup() => EditGroupForm.ShowDialog(this); public void LoadModel(TileGroupModel model) { groupModel = model; isChecked = groupModel.IsExpanded; label1.Text = " " + groupModel?.Title; label1.Image = (isChecked ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16); this.SetGridSize(groupModel.GridSize.Width, groupModel.GridSize.Height); this.LoadTiles(model.Items); this.SetGridSize(groupModel.GridSize.Width, groupModel.GridSize.Height); this.Invalidate(); } public void UpdateModel(TileGroupModel model) { groupModel = model; isChecked = groupModel.IsExpanded; label1.Text = " " + groupModel?.Title; this.Invalidate(); } public void LoadTiles(List tiles) { if (tiles == null) { return; } if (tiles.Count() <= 0) { return; } foreach (TileModel item in tiles) { // resolve final grid position Point? confirmedPosition = resolveCoord(item.Position); if (confirmedPosition == null) { continue; } // place control TilePanel panel = new TilePanel(); panel.LoadInfo(item); panel.Location = convertCoordToLocation(confirmedPosition.Value); items.Add(new Item() { Tile = panel, Coord = confirmedPosition.Value }); this.Controls.Add(panel); } } public void MoveTile(TilePanel panel, int posX, int posY) { Item item = items.Where(x => x.Tile.Equals(panel)).FirstOrDefault(); if (item == null) { return; } Point newPosition = convertLocationToCoord_Nearest(posX, posY); if (!isTileInBounds(newPosition)) { return; } if (hasTile(newPosition)) { Item swapItem = items.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 MoveTop() { if (this.FlowLayoutPanel == null) { return; } this.FlowLayoutPanel.Controls.SetChildIndex(this, 0); } public void MoveUp() { if (this.FlowLayoutPanel == null) { return; } 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) { return; } 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) { return; } this.FlowLayoutPanel.Controls.SetChildIndex(this, (this.FlowLayoutPanel.Controls.Count - 1)); } public void Remove() { if (this.FlowLayoutPanel == null) { return; } this.FlowLayoutPanel.Controls.Remove(this); } public void Remove(TilePanel panel) { Item m = items.Where(x => x.Tile.Equals(panel)).FirstOrDefault(); if (m != null) { items.Remove(m); } this.Controls.Remove(panel); } public void SetGridSize(int width, int height) { gridSize = new Point(width, height); groupModel.GridSize = new Size(groupModel.GridSize.Width, height); expandedHeight = (this.TileSize * height) + labelHeight; int w = CalcWidth(gridSize.X); this.Size = new Size(w, (isChecked ? this.ExpandedHeight : this.CollapseHeight)); } 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.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 convertLocationToCoord_Nearest(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 Point? findFirstFreeCoord() { Point gridSize = this.GridSize; for (int y = 0; y < gridSize.Y; y++) { for (int x = 0; x < 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 (items.Count >= (gridSize.X * gridSize.Y)) { return null; } if (items.Count <= 0) { return findFirstFreeCoord(); } // only one available if (items.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 bool hasTile(Point position) { if (items == null) { return false; } if (items.Count <= 0) { return false; } return (items.Count(x => x.Coord.Equals(position)) > 0); } protected bool isTileInBounds(Point position) { Point gridSize = this.GridSize; if (position.X >= gridSize.X) { return false; } if (position.Y >= gridSize.Y) { return false; } return true; } protected Point? resolveCoord(Point coord) { if (!isTileInBounds(coord)) { return null; } if (!hasTile(coord)) { return coord; } return resolveNextCoord(coord); } protected Point? resolveNextCoord(Point coord) { Point gridSize = this.GridSize; Point newCoord = coord; while (true) { newCoord.X++; if (newCoord.X >= gridSize.X) { newCoord.Y++; newCoord.X = 0; } if (!isTileInBounds(newCoord)) { return null; } if (hasTile(newCoord)) { continue; } return newCoord; } } private async void label1_MouseClick(object sender, MouseEventArgs e) { if (isAnimating) return; if (e.Button == MouseButtons.Left) { isChecked = !isChecked; label1.Image = (isChecked ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16); this.Invalidate(); await this.InvalidateContainer(); // exclusivity if (isChecked) { 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 = (isChecked ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16); } else if (e.Button == MouseButtons.Right) { contextMenuStrip2.Show(this, e.Location); } } #region tile context menu /// /// Add tile /// /// /// private void addTileMenuItem_Click(object sender, EventArgs e) { Point coord = convertLocationToCoord(lastMousePosition.X, lastMousePosition.Y); EditTileForm.ShowAddDialog(this, coord); } /// /// Add folder /// /// /// private void addListTileMenuItem_Click(object sender, EventArgs e) { Point coord = convertLocationToCoord(lastMousePosition.X, lastMousePosition.Y); EditTileFolderForm.ShowAddDialog(this, coord); } #endregion #region group context menu /// /// Add group /// /// /// private void addGroupMenuItem_Click(object sender, EventArgs e) { this.AddGroup(); } /// /// Edit group /// /// /// private void editGroupMenuItem_Click(object sender, EventArgs e) { this.EditGroup(); } /// /// Add row /// /// /// private void toolStripMenuItem5_Click(object sender, EventArgs e) { this.AddRow(); } /// /// Remove row /// /// /// private void removeRowToolStripMenuItem_Click_1(object sender, EventArgs e) { if (gridSize.Y <= 1) { return; } bool rs = items.Exists(x => x.Coord.Y.Equals(gridSize.Y - 1)); if (rs) { return; } this.SetGridSize(gridSize.X, (gridSize.Y - 1)); } /// /// Move to top /// /// /// private void moveTopMenuItem_Click(object sender, EventArgs e) { this.MoveTop(); } /// /// Move up /// /// /// private void moveUpMenuItem_Click(object sender, EventArgs e) { this.MoveUp(); } /// /// Move down /// /// /// private void moveDownMenuItem_Click(object sender, EventArgs e) { this.MoveDown(); } /// /// Move to bottom /// /// /// private void moveBottomMenuItem_Click(object sender, EventArgs e) { this.MoveBottom(); } /// /// Remove group /// /// /// private void removeGroupMenuItem3_Click(object sender, EventArgs e) { this.Remove(); } #endregion } }