-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMouseAvoidText.cs
More file actions
313 lines (275 loc) · 10.9 KB
/
MouseAvoidText.cs
File metadata and controls
313 lines (275 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
using Godot;
using System;
using System.Collections.Generic;
[Tool]
[GlobalClass]
public partial class MouseAvoidText : Control
{
[ExportGroup("Label Settings")]
[Export(PropertyHint.MultilineText)]
public string Text { get; set; } // 显示的文本内容
[Export(PropertyHint.LocaleId)]
public string Language { get; set; } = "en"; // 文本语言设置
[Export] public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Center; // 水平对齐方式
[Export] public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Center; // 垂直对齐方式
[ExportGroup("Font Settings")]
[Export]
public Font Font
{
get => _font;
set
{
if (value == null)
_font = GetThemeFont("font");
else
_font = value;
_fontRid = _font.GetRids();
}
}
Font _font;
[Export(PropertyHint.Range, "1,4096")]
public int FontSize { get; set; } = 16;
[Export] public Color FontColor { get; set; } = Colors.White;
[ExportGroup("Avoidance Settings")]
[Export] public float AvoidRadius { get; set; } = 1000f; // 鼠标避让半径
[Export] public float MaxOffset { get; set; } = 100f; // 最大避让偏移量
[Export] public float SmoothFactor { get; set; } = 10f; // 避让动画的平滑系数
// 私有变量
Vector2 _mousePosition; // 存储当前鼠标位置
Vector2[] _originalOffsets; // 存储字符原始偏移量
Vector2[] _currentOffsets; // 存储字符当前偏移量
readonly List<Vector2> _glyphPositions = []; // 存储所有字符的位置
readonly TextServer _textServer = TextServerManager.GetPrimaryInterface();
Rid _shapedBuffer;
Godot.Collections.Array<Rid> _fontRid;
// 节点准备就绪时调用
public override void _Ready()
{
// 设置纹理过滤模式为最近邻(保持像素清晰)
TextureFilter = TextureFilterEnum.Nearest;
Font ??= GetThemeFont("font");
}
// 每帧调用的处理函数
public override void _Process(double delta)
{
// 获取当前鼠标的全局位置
_mousePosition = GetGlobalMousePosition();
// 更新文本偏移量
UpdateTextOffsets();
}
// 更新文本字符偏移量的核心方法
private void UpdateTextOffsets()
{
// 如果文本为空则直接返回
if (string.IsNullOrEmpty(Text) || Font == null)
return;
// 创建文本缓冲区
_shapedBuffer = _textServer.CreateShapedText();
// 向缓冲区添加文本字符串
_textServer.ShapedTextAddString(
_shapedBuffer,
Text,
_fontRid,
FontSize,
opentypeFeatures: null,
language: Language,
meta: default
);
// 获取所有字符信息
var glyphs = _textServer.ShapedTextGetGlyphs(_shapedBuffer);
AlignGlyphPositions(glyphs);
// 初始化偏移数组(第一次或字符数量变化时)
if (_originalOffsets == null || _originalOffsets.Length != _glyphPositions.Count)
{
_originalOffsets = new Vector2[_glyphPositions.Count];
_currentOffsets = new Vector2[_glyphPositions.Count];
Array.Fill(_originalOffsets, Vector2.Zero);
Array.Fill(_currentOffsets, Vector2.Zero);
}
// 计算每个字符的目标偏移量(避让效果)
for (int i = 0; i < _glyphPositions.Count; i++)
{
// 计算字符的全局位置
var glyphGlobalPos = GlobalPosition + _glyphPositions[i];
// 计算字符到鼠标的向量
var toMouse = glyphGlobalPos - _mousePosition;
// 计算距离
var distance = toMouse.Length();
// 如果鼠标在避让半径内
if (distance < AvoidRadius)
{
// 计算避让方向和强度
var direction = toMouse.Normalized();
var strength = 1 - (distance / AvoidRadius);
strength = Mathf.Clamp(strength, 0, 1);
// 计算目标偏移量(方向*最大偏移*强度)
var targetOffset = direction * MaxOffset * strength;
// 平滑过渡到目标偏移
_currentOffsets[i] = _currentOffsets[i].Lerp(targetOffset, (float)GetProcessDeltaTime() * SmoothFactor);
}
else
{
// 平滑回到原始位置(无偏移)
_currentOffsets[i] = _currentOffsets[i].Lerp(Vector2.Zero, (float)GetProcessDeltaTime() * SmoothFactor);
}
}
// 请求重绘
QueueRedraw();
}
// 计算文本位置,调整排版
private void AlignGlyphPositions(Godot.Collections.Array<Godot.Collections.Dictionary> glyphs)
{
// 遍历每个字符计算位置
// 先遍历一次,拆分所有行,记录每行的glyph索引范围和宽度
var lines = new List<(int start, int end, float width, float ascent, float descent)>();
int lineStart = 0;
float lineWidth = 0f, maxAscent = 0f, maxDescent = 0f;
for (int i = 0; i < glyphs.Count; i++)
{
var glyph = glyphs[i];
var advance = (float)glyph["advance"];
var index = (int)glyph["index"];
float ascent = Font.GetAscent(FontSize);
float descent = Font.GetDescent(FontSize);
if (ascent > maxAscent) maxAscent = ascent;
if (descent > maxDescent) maxDescent = descent;
// 检查换行
if (advance == 0 && index == 0 && i != 0)
{
lines.Add((lineStart, i, lineWidth, maxAscent, maxDescent));
lineStart = i + 1;
lineWidth = 0f;
maxAscent = 0f;
maxDescent = 0f;
continue;
}
lineWidth += advance;
}
if (lineStart < glyphs.Count)
lines.Add((lineStart, glyphs.Count, lineWidth, maxAscent, maxDescent));
// 计算总文本高度
float totalTextHeight = 0f;
foreach (var (start, end, width, ascent, descent) in lines)
totalTextHeight += ascent + descent;
// 逐行排版
// 计算垂直分布(Fill模式)
_glyphPositions.Clear();
float[] lineYs = new float[lines.Count];
if (VerticalAlignment == VerticalAlignment.Fill && lines.Count > 1)
{
float totalLineHeight = 0f;
foreach (var (start, end, width, ascent, descent) in lines)
totalLineHeight += ascent + descent;
float lineSpacing = (Size.Y - totalLineHeight) / (lines.Count - 1);
float curY = 0f;
for (int l = 0; l < lines.Count; l++)
{
lineYs[l] = curY;
curY += lines[l].ascent + lines[l].descent + lineSpacing;
}
}
else
{
float startY = VerticalAlignment switch
{
VerticalAlignment.Top => 0f,
VerticalAlignment.Center => (Size.Y - totalTextHeight) / 2f,
VerticalAlignment.Bottom => Size.Y - totalTextHeight,
_ => 0f,
};
float curY = startY;
for (int l = 0; l < lines.Count; l++)
{
lineYs[l] = curY;
curY += lines[l].ascent + lines[l].descent;
}
}
// 每行处理
for (int lineIdx = 0; lineIdx < lines.Count; lineIdx++)
{
var (start, end, width, ascent, _) = lines[lineIdx];
float startX;
float[] charXs = new float[end - start];
// 水平 Fill
if (HorizontalAlignment == HorizontalAlignment.Fill && (end - start) > 1)
{
// 计算所有字符宽度
float totalCharWidth = 0f;
for (int i = start; i < end; i++)
{
var glyph = glyphs[i];
totalCharWidth += (float)glyph["advance"];
}
float charSpacing = (Size.X - totalCharWidth) / (end - start - 1);
float x = 0f;
for (int i = start, c = 0; i < end; i++, c++)
{
charXs[c] = x;
x += (float)glyphs[i]["advance"] + charSpacing;
}
}
else
{
// 普通对齐
startX = HorizontalAlignment switch
{
HorizontalAlignment.Left => 0f,
HorizontalAlignment.Center => (Size.X - width) / 2f,
HorizontalAlignment.Right => Size.X - width,
_ => 0f,
};
float x = startX;
for (int i = start, c = 0; i < end; i++, c++)
{
charXs[c] = x;
x += (float)glyphs[i]["advance"];
}
}
// 填充_glyphPositions
for (int i = start, c = 0; i < end; i++, c++)
{
var glyph = glyphs[i];
var offset = (Vector2)glyph["offset"];
_glyphPositions.Add(new Vector2(charXs[c], lineYs[lineIdx] + ascent) + offset);
}
}
}
// 自定义绘制方法
public override void _Draw()
{
// 如果文本为空或没有字符位置数据则直接返回
if (string.IsNullOrEmpty(Text) || _glyphPositions.Count == 0)
return;
if (Font == null || !_shapedBuffer.IsValid)
return;
// 绘制每个字符,应用计算好的偏移量
var glyphs = _textServer.ShapedTextGetGlyphs(_shapedBuffer);
int glyphPosIndex = 0; // 字符位置索引
for (int i = 0; i < glyphs.Count; i++)
{
var glyph = glyphs[i];
var fontRid = (Rid)glyph["font_rid"]; // 字体资源ID
var index = (int)glyph["index"]; // 字符索引
var offset = (Vector2)glyph["offset"]; // 字符偏移量
var advance = (float)glyph["advance"]; // 字符前进量
// 跳过换行符(与UpdateTextOffsets保持一致)
if (advance == 0 && index == 0 && i != 0)
continue;
// 计算最终字符位置(基础位置+避让偏移)
var glyphPos = _glyphPositions[glyphPosIndex] + _currentOffsets[glyphPosIndex];
// 如果字体有效则绘制字符
if (fontRid.IsValid)
{
_textServer.FontDrawGlyph(
fontRid,
GetCanvasItem(),
FontSize,
glyphPos + offset, // 应用字符自身的偏移量
index,
FontColor
);
}
glyphPosIndex++;
}
}
}