Skip to content

Commit 87a3838

Browse files
authored
fix: refactor editor custom cursors to only create each tool cursor once (#2459)
* fix: refactor editor custom cursors to only create each tool cursor once * fix: make cursor creation non-fatal
1 parent 11024fc commit 87a3838

File tree

4 files changed

+138
-52
lines changed

4 files changed

+138
-52
lines changed

Intersect.Editor/Configuration/ToolCursor.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace Intersect.Editor.Configuration;
55

66
public sealed class ToolCursor
77
{
8+
public const string CursorsFolder = "resources/cursors";
9+
810
public Point CursorClickPoint { get; set; }
911

1012
private static readonly Dictionary<EditingTool, ToolCursor> _toolCursorDict;
@@ -26,33 +28,32 @@ static ToolCursor()
2628

2729
public static void Load()
2830
{
29-
const string cursorFolder = "resources/cursors/";
30-
31-
if (!Directory.Exists(cursorFolder))
31+
if (!Directory.Exists(CursorsFolder))
3232
{
3333
return;
3434
}
3535

3636
foreach (EditingTool tool in Enum.GetValues(typeof(EditingTool)))
3737
{
38-
var fileName = $"{cursorFolder}editor_{tool.ToString().ToLowerInvariant()}.json";
38+
var fileName = $"editor_{tool.ToString().ToLowerInvariant()}.json";
39+
var filePath = Path.Combine(CursorsFolder, fileName);
3940
ToolCursor toolCursor;
4041

41-
if (File.Exists(fileName))
42+
if (File.Exists(filePath))
4243
{
4344
if (!_toolCursorDict.TryGetValue(tool, out toolCursor))
4445
{
4546
continue;
4647
}
4748

48-
var json = File.ReadAllText(fileName);
49+
var json = File.ReadAllText(filePath);
4950
toolCursor = JsonConvert.DeserializeObject<ToolCursor>(json);
5051
}
5152
else
5253
{
5354
toolCursor = new ToolCursor();
5455
var json = JsonConvert.SerializeObject(toolCursor);
55-
File.WriteAllText(fileName, json);
56+
File.WriteAllText(filePath, json);
5657
}
5758

5859
_toolCursorDict[tool] = toolCursor;

Intersect.Editor/Core/Preferences.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ namespace Intersect.Editor.Core;
77

88
public static partial class Preferences
99
{
10+
private static bool? _enableCursorSprites;
11+
12+
public static bool EnableCursorSprites
13+
{
14+
get => _enableCursorSprites ??= LoadPreferenceBool(nameof(EnableCursorSprites)) ?? false;
15+
set
16+
{
17+
if (_enableCursorSprites == value)
18+
{
19+
return;
20+
}
21+
22+
_enableCursorSprites = value;
23+
SavePreference(nameof(EnableCursorSprites), _enableCursorSprites.ToString() ?? string.Empty);
24+
}
25+
}
1026

1127
public static void SavePreference(string key, string value)
1228
{
@@ -18,6 +34,17 @@ public static void SavePreference(string key, string value)
1834
regkey.SetValue(key, value);
1935
}
2036

37+
private static bool? LoadPreferenceBool(string key)
38+
{
39+
var rawPreference = LoadPreference(key);
40+
if (string.IsNullOrWhiteSpace(rawPreference))
41+
{
42+
return null;
43+
}
44+
45+
return Convert.ToBoolean(rawPreference);
46+
}
47+
2148
public static string LoadPreference(string key)
2249
{
2350
var regkey = Registry.CurrentUser.OpenSubKey("Software", false);

Intersect.Editor/Forms/DockingElements/frmMapEditor.cs

Lines changed: 101 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,6 @@ public partial class FrmMapEditor : DockContent
3737
private bool mMapChanged;
3838

3939
// MapGrid Cursor
40-
private Bitmap mCurSprite;
41-
42-
private readonly string mCurFolder = "resources/cursors/";
43-
44-
private string mCurPath;
45-
46-
private Point mCurClickPoint;
47-
4840
private Timer cursorUpdateTimer;
4941

5042
public struct IconInfo
@@ -2356,17 +2348,12 @@ private void pnlMapContainer_Resize(object sender, EventArgs e)
23562348

23572349
private void picMap_MouseEnter(object sender, EventArgs e)
23582350
{
2359-
var enableCursorSprites = Preferences.LoadPreference("EnableCursorSprites");
2360-
23612351
if (!Globals.MapEditorWindow.DockPanel.Focused && Globals.CurrentEditor == -1)
23622352
{
23632353
Globals.MapEditorWindow.DockPanel.Focus();
23642354
}
23652355

2366-
if (!string.IsNullOrEmpty(enableCursorSprites) && !Convert.ToBoolean(enableCursorSprites))
2367-
{
2368-
RemoveSpriteCursorInGrid();
2369-
}
2356+
RemoveSpriteCursorInGrid();
23702357
}
23712358

23722359
private void picMap_MouseLeave(object sender, EventArgs e)
@@ -2387,55 +2374,128 @@ private void CursorUpdateTimer_Tick(object sender, EventArgs e)
23872374

23882375
private void SetCursorSpriteInGrid()
23892376
{
2390-
if (!Directory.Exists(mCurFolder))
2377+
if (!Preferences.EnableCursorSprites)
23912378
{
23922379
return;
23932380
}
23942381

2395-
var enableCursorSprites = Preferences.LoadPreference("EnableCursorSprites");
2382+
var currentTool = Globals.CurrentTool;
2383+
var toolCursor = GetOrCreateCursorForTool(currentTool);
2384+
Cursor = toolCursor ?? Cursors.Default;
2385+
}
23962386

2397-
if (!(!string.IsNullOrEmpty(enableCursorSprites) && Convert.ToBoolean(enableCursorSprites)))
2387+
private void RemoveSpriteCursorInGrid()
2388+
{
2389+
if (!_toolCursors.Contains(Cursor))
23982390
{
2391+
// Exit instead of setting the cursor to default if it's not a custom cursor
23992392
return;
24002393
}
2394+
2395+
Cursor = Cursors.Default;
2396+
}
24012397

2402-
mCurPath = $"{mCurFolder}editor_{Globals.CurrentTool.ToString().ToLowerInvariant()}.png";
2398+
private static Cursor? GetOrCreateCursorForTool(EditingTool editingTool)
2399+
{
2400+
if (_toolCursorCache.TryGetValue(editingTool, out var toolCursor))
2401+
{
2402+
return toolCursor;
2403+
}
24032404

2404-
if (!File.Exists(mCurPath))
2405+
if (!ToolCursor.ToolCursorDict.TryGetValue(editingTool, out var toolCursorInfo))
24052406
{
2406-
return;
2407+
var loadedClickPointKeys = string.Join(", ", ToolCursor.ToolCursorDict.Keys);
2408+
Log.Error(
2409+
$"Unable to load click point for {editingTool}, click points only exist for: {loadedClickPointKeys}"
2410+
);
2411+
return null;
24072412
}
24082413

2409-
mCurClickPoint = ToolCursor.ToolCursorDict[Globals.CurrentTool].CursorClickPoint;
2410-
mCurSprite = new Bitmap(mCurPath);
2411-
Cursor = CreateCursorInGrid(mCurSprite, mCurClickPoint);
2412-
}
2414+
if (!Directory.Exists(ToolCursor.CursorsFolder))
2415+
{
2416+
return null;
2417+
}
24132418

2414-
private void RemoveSpriteCursorInGrid()
2415-
{
2416-
if (mCurSprite == default)
2419+
var cursorFileName = $"editor_{editingTool.ToString().ToLowerInvariant()}.png";
2420+
var cursorPath = Path.Combine(ToolCursor.CursorsFolder, cursorFileName);
2421+
var cursorAbsolutePath = Path.GetFullPath(cursorPath);
2422+
var loggingCursorPath = cursorPath;
2423+
#if DEBUG
2424+
loggingCursorPath = cursorAbsolutePath;
2425+
#endif
2426+
if (!File.Exists(cursorAbsolutePath))
24172427
{
2418-
return;
2428+
Log.Error(
2429+
$"Custom cursor texture '{cursorFileName}' does not exist in {ToolCursor.CursorsFolder} resolved to {loggingCursorPath}"
2430+
);
2431+
return null;
24192432
}
24202433

2421-
mCurSprite.Dispose();
2422-
DestroyIcon(Cursor.Handle);
2423-
Cursor = Cursors.Default;
2434+
Bitmap cursorBitmap;
2435+
try
2436+
{
2437+
cursorBitmap = new Bitmap(cursorAbsolutePath);
2438+
}
2439+
catch (Exception exception)
2440+
{
2441+
Log.Error(exception, $"Failed to load custom cursor for {editingTool} resolved to {loggingCursorPath}");
2442+
return null;
2443+
}
2444+
2445+
toolCursor = CreateCursorInGrid(cursorBitmap, toolCursorInfo.CursorClickPoint, cursorFileName);
2446+
if (toolCursor == null)
2447+
{
2448+
return null;
2449+
}
2450+
2451+
_toolCursors.Add(toolCursor);
2452+
_toolCursorCache[editingTool] = toolCursor;
2453+
2454+
return toolCursor;
24242455
}
24252456

24262457
/// <summary>
24272458
/// Creates a cursor from a bitmap depending on the user preferences and selected tool.
24282459
/// </summary>
2429-
private Cursor CreateCursorInGrid(Bitmap bmp, Point curHotSpot)
2460+
private static Cursor? CreateCursorInGrid(Bitmap cursorBitmap, Point cursorClickPoint, string logName)
24302461
{
2431-
DestroyIcon(Cursor.Handle);
2432-
IntPtr ptr = bmp.GetHicon();
2433-
IconInfo tmp = new IconInfo();
2434-
GetIconInfo(ptr, ref tmp);
2435-
tmp.XHotspot = curHotSpot.X;
2436-
tmp.YHotspot = curHotSpot.Y;
2437-
tmp.FIcon = false;
2438-
ptr = CreateIconIndirect(ref tmp);
2439-
return new Cursor(ptr);
2462+
try
2463+
{
2464+
IntPtr bitmapHicon = cursorBitmap.GetHicon();
2465+
if (bitmapHicon == IntPtr.Zero)
2466+
{
2467+
Log.Warn($"Failed to get bitmap icon handle for {logName}");
2468+
return null;
2469+
}
2470+
2471+
IconInfo cursorIconInfo = new IconInfo();
2472+
if (!GetIconInfo(bitmapHicon, ref cursorIconInfo))
2473+
{
2474+
Log.Warn($"Failed to get icon info for {logName}");
2475+
return null;
2476+
}
2477+
2478+
cursorIconInfo.XHotspot = cursorClickPoint.X;
2479+
cursorIconInfo.YHotspot = cursorClickPoint.Y;
2480+
cursorIconInfo.FIcon = false;
2481+
2482+
var cursorIcon = CreateIconIndirect(ref cursorIconInfo);
2483+
// ReSharper disable once InvertIf
2484+
if (cursorIcon == IntPtr.Zero)
2485+
{
2486+
Log.Warn($"Failed to create cursor icon for {logName}");
2487+
return null;
2488+
}
2489+
2490+
return new Cursor(cursorIcon);
2491+
}
2492+
catch (Exception exception)
2493+
{
2494+
Log.Error(exception, $"Error while creating cursor for {logName}");
2495+
return null;
2496+
}
24402497
}
2498+
2499+
private static readonly HashSet<Cursor> _toolCursors = [];
2500+
private static readonly Dictionary<EditingTool, Cursor> _toolCursorCache = [];
24412501
}

Intersect.Editor/Forms/frmOptions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ private void InitForm()
2626

2727
// Load settings for the General tab
2828
var suppressTilesetWarning = Preferences.LoadPreference("SuppressTextureWarning");
29-
var enableCursorSprites = Preferences.LoadPreference("EnableCursorSprites");
3029

3130
if (suppressTilesetWarning == "")
3231
{
@@ -37,8 +36,7 @@ private void InitForm()
3736
chkSuppressTilesetWarning.Checked = Convert.ToBoolean(suppressTilesetWarning);
3837
}
3938

40-
chkCursorSprites.Checked = !string.IsNullOrEmpty(enableCursorSprites) &&
41-
Convert.ToBoolean(enableCursorSprites);
39+
chkCursorSprites.Checked = Preferences.EnableCursorSprites;
4240

4341
txtGamePath.Text = Preferences.LoadPreference("ClientPath");
4442

@@ -95,7 +93,7 @@ private void InitLocalization()
9593
private void frmOptions_FormClosing(object sender, FormClosingEventArgs e)
9694
{
9795
Preferences.SavePreference("SuppressTextureWarning", chkSuppressTilesetWarning.Checked.ToString());
98-
Preferences.SavePreference("EnableCursorSprites", chkCursorSprites.Checked.ToString());
96+
Preferences.EnableCursorSprites = chkCursorSprites.Checked;
9997
Preferences.SavePreference("ClientPath", txtGamePath.Text);
10098
Preferences.SavePreference("PackageUpdateAssets", chkPackageAssets.Checked.ToString());
10199
Preferences.SavePreference("SoundPackSize", nudSoundBatch.Value.ToString(CultureInfo.InvariantCulture));

0 commit comments

Comments
 (0)