1
1
using System ;
2
- using System . IO ;
2
+ using System . Collections . Generic ;
3
+ using System . Linq ;
3
4
using System . Numerics ;
5
+ using System . Text . RegularExpressions ;
4
6
using ImGuiNET ;
5
7
using Veldrid ;
6
8
@@ -11,14 +13,24 @@ public class ShaderEditor : IImGuiComponent
11
13
{
12
14
public const int MAXEDITORSTRINGLENGTH = 1000000 ; // man this is shitty tho
13
15
private const float AUTOAPPLYINTERVAL = 1f ;
16
+ private const float FPSUPDATEINTERVAL = 0.25f ;
14
17
private const float HIDEUIHELPTEXTDURATION = 5f ;
15
18
16
19
private bool showUI = true ;
17
20
private float hideUIHelpTextDelta = 0 ;
18
21
private bool autoApply = true ;
19
22
private float autoApplyCurrentInterval = 0 ;
20
23
21
- private string errorMessage ;
24
+ private bool basicMode = false ;
25
+ private int editorSelectedLineIndex = - 1 ;
26
+ private string editorSelectedLineContent = null ;
27
+ private int editorSelectedLineCursorPosition = - 1 ;
28
+
29
+ private string fps = "" ;
30
+ private float fpsUpdateCurrentInterval = 0 ;
31
+
32
+ private string previousError = null ;
33
+ private readonly List < string > errorMessages = new List < string > ( ) ;
22
34
23
35
private string fragmentCode = @"// Available inputs
24
36
// mouse (vec4) : x,y => position, z => mouse 1 down, z => mouse 2 down
@@ -33,6 +45,7 @@ void main()
33
45
float y = gl_FragCoord.y / resolution.y;
34
46
out_Color = vec4(0,x,y,1);
35
47
}" ;
48
+ private readonly List < string > fragmentCodeLines = new List < string > ( ) ;
36
49
37
50
public ImGuiController Controller { get ; private set ; }
38
51
public string FragmentCode => fragmentCode ;
@@ -44,6 +57,7 @@ public ShaderEditor(ImGuiController controller)
44
57
45
58
public void Initialize ( )
46
59
{
60
+ SplitLines ( ) ;
47
61
Apply ( ) ;
48
62
}
49
63
@@ -132,43 +146,195 @@ private void SubmitMainMenu(float deltaTime)
132
146
{
133
147
autoApplyCurrentInterval = 0 ;
134
148
}
135
-
136
- string fps = $ "{ ( int ) MathF . Round ( 1f / deltaTime ) } ";
149
+ fpsUpdateCurrentInterval += deltaTime ;
150
+ if ( fpsUpdateCurrentInterval >= FPSUPDATEINTERVAL )
151
+ {
152
+ fpsUpdateCurrentInterval = 0 ;
153
+ fps = $ "{ ( int ) MathF . Round ( 1f / deltaTime ) } ";
154
+ }
137
155
Vector2 fpsSize = ImGui . CalcTextSize ( fps ) ;
138
156
ImGui . SameLine ( ImGui . GetWindowWidth ( ) - fpsSize . X - 20 ) ;
139
157
ImGui . Text ( fps ) ;
140
158
ImGui . EndMainMenuBar ( ) ;
141
159
}
142
160
}
143
161
144
- private void SubmitEditorWindow ( )
162
+ private unsafe void SubmitEditorWindow ( )
145
163
{
146
164
ImGui . PushStyleVar ( ImGuiStyleVar . WindowRounding , 0 ) ;
147
165
ImGui . SetNextWindowSizeConstraints ( Vector2 . One * 500 , Vector2 . One * Controller . Context . Width ) ;
148
166
if ( ImGui . Begin ( "Shader Editor" ) )
149
167
{
150
168
ImGui . PushFont ( Controller . EditorFont ) ;
151
- Vector2 editorWindowSize = ImGui . GetWindowSize ( ) ;
152
- float bottomMargin = 40 ;
153
- if ( errorMessage != null )
169
+ if ( ImGui . BeginTabBar ( "editor mode" ) )
154
170
{
155
- ImGui . PushTextWrapPos ( ) ;
156
- ImGui . TextColored ( RgbaFloat . Red . ToVector4 ( ) , errorMessage ) ;
157
- ImGui . PopTextWrapPos ( ) ;
158
- Vector2 errorSize = ImGui . GetItemRectSize ( ) ;
159
- bottomMargin = errorSize . Y * 2f + 15f ; // sshh no tears
171
+ if ( ImGui . BeginTabItem ( "Advanced" ) )
172
+ {
173
+ basicMode = false ;
174
+ SubmitAdvancedEditor ( ) ;
175
+ ImGui . EndTabItem ( ) ;
176
+ }
177
+ if ( ImGui . BeginTabItem ( "Basic" ) )
178
+ {
179
+ basicMode = true ;
180
+ if ( ImGui . BeginChild ( "editor basic" , Vector2 . Zero , true ) )
181
+ {
182
+ Vector2 editorWindowSize = ImGui . GetWindowSize ( ) ;
183
+ float textSize = ImGui . CalcTextSize ( fragmentCode ) . Y + 32 ;
184
+ ImGui . PushItemWidth ( - 1 ) ;
185
+ ImGui . InputTextMultiline ( "" ,
186
+ ref fragmentCode ,
187
+ MAXEDITORSTRINGLENGTH ,
188
+ new Vector2 ( editorWindowSize . X - 16 , textSize > editorWindowSize . Y ? textSize : editorWindowSize . Y - 16 ) ,
189
+ ImGuiInputTextFlags . AllowTabInput ) ;
190
+ ImGui . PopItemWidth ( ) ;
191
+ }
192
+ ImGui . EndTabItem ( ) ;
193
+ }
194
+ ImGui . EndTabBar ( ) ;
160
195
}
161
- ImGui . InputTextMultiline ( "" ,
162
- ref fragmentCode ,
163
- MAXEDITORSTRINGLENGTH ,
164
- new Vector2 ( editorWindowSize . X - 15 , editorWindowSize . Y - bottomMargin ) ,
165
- ImGuiInputTextFlags . AllowTabInput ) ;
166
196
ImGui . PopFont ( ) ;
167
197
ImGui . End ( ) ;
168
198
}
199
+ if ( errorMessages . Count != 0 )
200
+ {
201
+ ImGui . SetNextWindowSizeConstraints ( new Vector2 ( 500 , 16 ) , new Vector2 ( 500 , 500 ) ) ;
202
+ ImGui . BeginTooltip ( ) ;
203
+ ImGui . PushTextWrapPos ( ) ;
204
+ for ( int i = 0 ; i < errorMessages . Count ; i ++ )
205
+ {
206
+ string errorMessage = errorMessages [ i ] ;
207
+ if ( string . IsNullOrWhiteSpace ( errorMessage ) ) continue ;
208
+ ImGui . TextColored ( RgbaFloat . Red . ToVector4 ( ) , errorMessage ) ;
209
+ }
210
+ ImGui . PopTextWrapPos ( ) ;
211
+ ImGui . EndTooltip ( ) ;
212
+ }
169
213
ImGui . PopStyleVar ( ) ;
170
214
}
171
215
216
+ private unsafe void SubmitAdvancedEditor ( )
217
+ {
218
+ if ( ImGui . BeginChild ( "editor" , Vector2 . Zero , true ) )
219
+ {
220
+ // handle basic input
221
+ if ( editorSelectedLineIndex != - 1 )
222
+ {
223
+ if ( ( editorSelectedLineCursorPosition == 0 && ImGui . IsKeyPressed ( ImGui . GetKeyIndex ( ImGuiKey . LeftArrow ) , false ) )
224
+ || ImGui . IsKeyPressed ( ImGui . GetKeyIndex ( ImGuiKey . UpArrow ) , true ) )
225
+ {
226
+ SetSelectedLine ( editorSelectedLineIndex - 1 ) ;
227
+ }
228
+ if ( ( editorSelectedLineCursorPosition == editorSelectedLineContent . Length && ImGui . IsKeyPressed ( ImGui . GetKeyIndex ( ImGuiKey . RightArrow ) , false ) )
229
+ || ImGui . IsKeyPressed ( ImGui . GetKeyIndex ( ImGuiKey . DownArrow ) , true ) )
230
+ {
231
+ SetSelectedLine ( editorSelectedLineIndex + 1 ) ;
232
+ }
233
+ if ( ImGui . IsKeyPressed ( ImGui . GetKeyIndex ( ImGuiKey . Enter ) , true ) )
234
+ {
235
+ string newLineContent = "" ;
236
+ if ( editorSelectedLineCursorPosition != editorSelectedLineContent . Length )
237
+ {
238
+ fragmentCodeLines [ editorSelectedLineIndex ] = editorSelectedLineContent . Take ( editorSelectedLineCursorPosition ) . ToSystemString ( ) ;
239
+ newLineContent = editorSelectedLineContent . Skip ( editorSelectedLineCursorPosition ) . ToSystemString ( ) ;
240
+ }
241
+ fragmentCodeLines . Insert ( editorSelectedLineIndex + 1 , newLineContent ) ;
242
+ SetSelectedLine ( editorSelectedLineIndex + 1 ) ;
243
+ }
244
+ if ( editorSelectedLineIndex > 0 )
245
+ {
246
+ if ( editorSelectedLineCursorPosition == 0 && ImGui . IsKeyPressed ( ImGui . GetKeyIndex ( ImGuiKey . Backspace ) , true ) )
247
+ {
248
+ if ( ! string . IsNullOrEmpty ( editorSelectedLineContent ) )
249
+ {
250
+ fragmentCodeLines [ editorSelectedLineIndex - 1 ] += editorSelectedLineContent ;
251
+ }
252
+ fragmentCodeLines . RemoveAt ( editorSelectedLineIndex ) ;
253
+ SetSelectedLine ( editorSelectedLineIndex - 1 ) ;
254
+ }
255
+ }
256
+ }
257
+
258
+ // draw lines
259
+ for ( int i = 0 ; i < fragmentCodeLines . Count ; i ++ )
260
+ {
261
+ string line = fragmentCodeLines [ i ] ;
262
+ string lineNumber = $ "{ i + Controller . Context . FragmentHeaderLineCount } ";
263
+ bool isError = errorMessages . Any ( msg => msg . StartsWith ( $ "{ lineNumber } :") ) ;
264
+ bool isEdited = i == editorSelectedLineIndex ;
265
+ ImGui . TextColored (
266
+ isEdited ? RgbaFloat . Green . ToVector4 ( ) : isError ? RgbaFloat . Red . ToVector4 ( ) : RgbaFloat . LightGrey . ToVector4 ( ) ,
267
+ lineNumber ) ;
268
+ ImGui . SameLine ( 50 ) ;
269
+ if ( isError )
270
+ {
271
+ ImGui . PushStyleColor ( ImGuiCol . Text , RgbaFloat . Red . ToVector4 ( ) ) ;
272
+ }
273
+ if ( isEdited )
274
+ {
275
+ ImGui . PushStyleVar ( ImGuiStyleVar . FramePadding , Vector2 . Zero ) ;
276
+ ImGui . PushItemWidth ( - 1 ) ;
277
+ // taken from https://github.com/mellinoe/ImGui.NET/blob/0b9c9ea07d720ac0c4e382deb8f08de30703a9a3/src/ImGui.NET.SampleProgram/MemoryEditor.cs#L128
278
+ // which is not ideal
279
+ ImGuiInputTextCallback callback = ( data ) =>
280
+ {
281
+ int * p_cursor_pos = ( int * ) data ->UserData ;
282
+
283
+ if ( ImGuiNative . ImGuiInputTextCallbackData_HasSelection ( data ) == 0 )
284
+ * p_cursor_pos = data ->CursorPos ;
285
+ return 0 ;
286
+ } ;
287
+ int cursorPos = - 1 ;
288
+ const ImGuiInputTextFlags flags = ImGuiInputTextFlags . AllowTabInput | ImGuiInputTextFlags . CallbackAlways ;
289
+ if ( ImGui . InputText ( lineNumber ,
290
+ ref editorSelectedLineContent ,
291
+ 1000 ,
292
+ flags ,
293
+ callback ,
294
+ ( IntPtr ) ( & cursorPos ) ) )
295
+ {
296
+ fragmentCodeLines [ editorSelectedLineIndex ] = editorSelectedLineContent ;
297
+ }
298
+ ImGui . PopItemWidth ( ) ;
299
+ ImGui . PopStyleVar ( 1 ) ;
300
+ editorSelectedLineCursorPosition = cursorPos ;
301
+ }
302
+ else if ( ImGui . Selectable ( line ) )
303
+ {
304
+ SetSelectedLine ( i ) ;
305
+ }
306
+ if ( isError )
307
+ {
308
+ ImGui . PopStyleColor ( 1 ) ;
309
+ }
310
+ }
311
+ ImGui . EndChild ( ) ;
312
+ }
313
+ }
314
+
315
+ private void SetSelectedLine ( int i )
316
+ {
317
+ if ( i >= 0 && i < fragmentCodeLines . Count )
318
+ {
319
+ editorSelectedLineIndex = i ;
320
+ ImGui . SetKeyboardFocusHere ( 1 ) ;
321
+ editorSelectedLineContent = fragmentCodeLines [ i ] ;
322
+ }
323
+ }
324
+
325
+ private void SplitLines ( )
326
+ {
327
+ editorSelectedLineIndex = - 1 ;
328
+ editorSelectedLineContent = null ;
329
+ fragmentCodeLines . Clear ( ) ;
330
+ fragmentCodeLines . AddRange ( Regex . Split ( fragmentCode , "\r \n |\r |\n " ) ) ;
331
+ }
332
+
333
+ private void MergeLines ( )
334
+ {
335
+ fragmentCode = string . Join ( "\n " , fragmentCodeLines ) ;
336
+ }
337
+
172
338
public void Update ( float deltaTime )
173
339
{
174
340
if ( autoApply )
@@ -184,17 +350,35 @@ public void Update(float deltaTime)
184
350
185
351
public void SetError ( string error )
186
352
{
187
- errorMessage = error ;
353
+ if ( error == previousError ) return ;
354
+ errorMessages . Clear ( ) ;
355
+ if ( error != null )
356
+ {
357
+ errorMessages . AddRange ( Regex . Split (
358
+ error . Replace ( "Compilation failed: " , "" )
359
+ . Replace ( "<veldrid-spirv-input>:" , "" ) ,
360
+ "\r \n |\r |\n " ) ) ;
361
+ }
362
+ previousError = error ;
188
363
}
189
364
190
365
public void Apply ( )
191
366
{
367
+ if ( basicMode )
368
+ {
369
+ SplitLines ( ) ;
370
+ }
371
+ else
372
+ {
373
+ MergeLines ( ) ;
374
+ }
192
375
Controller . Context . CreateDynamicResources ( fragmentCode ) ;
193
376
}
194
377
195
378
public void LoadShader ( string shaderContent )
196
379
{
197
380
fragmentCode = shaderContent ;
381
+ SplitLines ( ) ;
198
382
Apply ( ) ;
199
383
}
200
384
}
0 commit comments