Skip to content

Commit 90ac07a

Browse files
committed
Add icon selector feature to Intersect Editor
- Add FrmIconSelector form with paginated icon grid (160/page, 32px thumbnails) - Replace cmbPic dropdown in Item editor with Select Icon button + icon selector - Replace cmbSprite dropdown in Spell editor with Select Icon button + icon selector - Uses DarkUI controls, MemoryStream for file-lock-free image loading
1 parent 32d3913 commit 90ac07a

7 files changed

Lines changed: 448 additions & 81 deletions

File tree

Intersect.Editor/Forms/Editors/frmIconSelector.Designer.cs

Lines changed: 165 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Drawing;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Windows.Forms;
7+
8+
namespace Intersect.Editor.Forms.Editors
9+
{
10+
public partial class FrmIconSelector : Form
11+
{
12+
private const int IconsPerPage = 160; // Perfect fit for 16 columns x 10 rows
13+
private const int IconSize = 32;
14+
15+
private readonly string _iconDirectory;
16+
17+
private List<string> _allIcons = new List<string>();
18+
private List<string> _filteredIcons = new List<string>();
19+
private int _currentPage = 0;
20+
private PictureBox _selectedPictureBox;
21+
private List<PictureBox> _pictureBoxPool = new List<PictureBox>();
22+
23+
public string SelectedIcon { get; private set; }
24+
25+
public FrmIconSelector(string iconDirectory, string currentIcon = null)
26+
{
27+
InitializeComponent();
28+
29+
_iconDirectory = iconDirectory;
30+
SelectedIcon = currentIcon;
31+
32+
LoadIcons();
33+
UpdatePagination();
34+
LoadCurrentPage();
35+
}
36+
37+
private void LoadIcons()
38+
{
39+
if (!Directory.Exists(_iconDirectory)) return;
40+
41+
_allIcons = Directory.GetFiles(_iconDirectory, "*.png")
42+
.Concat(Directory.GetFiles(_iconDirectory, "*.jpg"))
43+
.Concat(Directory.GetFiles(_iconDirectory, "*.jpeg"))
44+
.ToList();
45+
46+
_filteredIcons = new List<string>(_allIcons);
47+
}
48+
49+
private void LoadCurrentPage()
50+
{
51+
flowIconPanel.SuspendLayout();
52+
53+
var startIndex = _currentPage * IconsPerPage;
54+
var endIndex = Math.Min(startIndex + IconsPerPage, _filteredIcons.Count);
55+
var itemsToDisplay = endIndex - startIndex;
56+
57+
// Dispose old images to prevent memory leaks and lag
58+
foreach (var picBox in _pictureBoxPool)
59+
{
60+
if (picBox.Image != null)
61+
{
62+
var img = picBox.Image;
63+
picBox.Image = null;
64+
img.Dispose();
65+
}
66+
}
67+
68+
// Ensure we have enough controls in the pool
69+
while (_pictureBoxPool.Count < itemsToDisplay)
70+
{
71+
var picBox = new PictureBox
72+
{
73+
Width = IconSize,
74+
Height = IconSize,
75+
SizeMode = PictureBoxSizeMode.StretchImage,
76+
Margin = new Padding(2),
77+
Cursor = Cursors.Hand,
78+
BackColor = System.Drawing.Color.FromArgb(60, 63, 65)
79+
};
80+
picBox.Click += IconButton_Click;
81+
_pictureBoxPool.Add(picBox);
82+
flowIconPanel.Controls.Add(picBox);
83+
}
84+
85+
int poolIndex = 0;
86+
for (int i = startIndex; i < endIndex; i++)
87+
{
88+
var iconPath = _filteredIcons[i];
89+
90+
Image img = null;
91+
try
92+
{
93+
// Use MemoryStream to avoid file locking on disk
94+
if (File.Exists(iconPath))
95+
{
96+
var bytes = File.ReadAllBytes(iconPath);
97+
using (var ms = new MemoryStream(bytes))
98+
{
99+
img = Image.FromStream(ms);
100+
}
101+
}
102+
}
103+
catch
104+
{
105+
continue;
106+
}
107+
108+
if (img == null) continue;
109+
110+
var picBox = _pictureBoxPool[poolIndex];
111+
picBox.Image = img;
112+
picBox.Tag = iconPath;
113+
picBox.Visible = true;
114+
115+
// Clear selection visuals if not selected
116+
if (SelectedIcon == Path.GetFileName(iconPath))
117+
{
118+
picBox.BorderStyle = BorderStyle.FixedSingle;
119+
picBox.BackColor = System.Drawing.Color.FromArgb(100, 100, 100);
120+
_selectedPictureBox = picBox;
121+
}
122+
else
123+
{
124+
picBox.BorderStyle = BorderStyle.None;
125+
picBox.BackColor = System.Drawing.Color.FromArgb(60, 63, 65);
126+
}
127+
128+
poolIndex++;
129+
}
130+
131+
// Hide unused controls in the pool
132+
for (int i = poolIndex; i < _pictureBoxPool.Count; i++)
133+
{
134+
_pictureBoxPool[i].Visible = false;
135+
}
136+
137+
flowIconPanel.ResumeLayout();
138+
}
139+
140+
private void UpdatePagination()
141+
{
142+
int totalPages = Math.Max(1, (int)Math.Ceiling(_filteredIcons.Count / (double)IconsPerPage));
143+
144+
lblPageInfo.Text = $"Page {_currentPage + 1} / {totalPages}";
145+
btnPrevPage.Enabled = _currentPage > 0;
146+
btnNextPage.Enabled = _currentPage < totalPages - 1;
147+
}
148+
149+
private void IconButton_Click(object sender, EventArgs e)
150+
{
151+
if (sender is PictureBox picBox && picBox.Tag is string iconPath)
152+
{
153+
// Clear previous selection
154+
if (_selectedPictureBox != null)
155+
{
156+
_selectedPictureBox.BorderStyle = BorderStyle.None;
157+
_selectedPictureBox.BackColor = System.Drawing.Color.FromArgb(60, 63, 65);
158+
}
159+
160+
// Set new selection with visual feedback
161+
_selectedPictureBox = picBox;
162+
picBox.BorderStyle = BorderStyle.FixedSingle;
163+
picBox.BackColor = System.Drawing.Color.FromArgb(100, 100, 100);
164+
SelectedIcon = Path.GetFileName(iconPath);
165+
}
166+
}
167+
168+
private void btnPrevPage_Click(object sender, EventArgs e)
169+
{
170+
_currentPage--;
171+
UpdatePagination();
172+
LoadCurrentPage();
173+
}
174+
175+
private void btnNextPage_Click(object sender, EventArgs e)
176+
{
177+
_currentPage++;
178+
UpdatePagination();
179+
LoadCurrentPage();
180+
}
181+
182+
private void btnOK_Click(object sender, EventArgs e)
183+
{
184+
if (!string.IsNullOrEmpty(SelectedIcon))
185+
{
186+
DialogResult = DialogResult.OK;
187+
Close();
188+
}
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)