Skip to content

Commit efe7a4b

Browse files
authored
Merge pull request #2 from Calebsem/feature/better-editor
Feature/better editor
2 parents ab3282c + 0777eca commit efe7a4b

6 files changed

+234
-28
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,13 @@ As you can guess, MacOS doesn't get a choice.
8282
## Known issues
8383

8484
- OpenGL switches y coordinates compared to the other APIs so things will be vertically switched
85-
- editor layout gets funky when there are a lot of compile errors
85+
- Vulkan doesn't like some shaders and can freeze the app on load
8686

8787
## Roadmap and contribution
8888

8989
Contributions are welcome, feel free to fork and open PRs. Here are some features that I wish to implement in the future:
9090

91-
- a way better code editor
91+
- editor with syntax highlight? Might be hard in the current state
9292
- input textures
9393
- input audio
9494
- more exotic inputs : game controllers, midi, OSC, why not some interaction with [OSSIA Score](https://ossia.io/)

YALCT/Extensions.cs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace YALCT
5+
{
6+
public static class StringExtensions
7+
{
8+
public static string ToSystemString(this IEnumerable<char> source)
9+
{
10+
return new string(source.ToArray());
11+
}
12+
}
13+
}

YALCT/ImGuiController.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class ImGuiController
2626
#else
2727
private bool fullscreen = true;
2828
#endif
29-
private float uiAlpha = 0.5f;
29+
private float uiAlpha = 0.75f;
3030

3131
public RuntimeContext Context => context;
3232
public ImFontPtr MainFont => mainFont;

YALCT/RuntimeContext.cs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Numerics;
33
using System.Text;
4+
using System.Text.RegularExpressions;
45
using ImGuiNET;
56
using Veldrid;
67
using Veldrid.Sdl2;
@@ -35,6 +36,7 @@ public class RuntimeContext : IDisposable
3536
private ShaderDescription vertexShaderDesc;
3637
private Shader[] shaders;
3738
private string currentFragmentShader;
39+
private int fragmentHeaderLineCount = -1;
3840

3941
private ImGuiRenderer imGuiRenderer;
4042
private ImGuiController uiController;
@@ -44,6 +46,17 @@ public class RuntimeContext : IDisposable
4446
public int Width => window.Width;
4547
public int Height => window.Height;
4648
public Sdl2Window Window => window;
49+
public int FragmentHeaderLineCount
50+
{
51+
get
52+
{
53+
if (fragmentHeaderLineCount == -1)
54+
{
55+
fragmentHeaderLineCount = Regex.Split(fragmentHeaderCode, "\r\n|\r|\n").Length;
56+
}
57+
return fragmentHeaderLineCount;
58+
}
59+
}
4760

4861
public RuntimeContext(GraphicsBackend backend)
4962
{
@@ -349,9 +362,7 @@ void main()
349362
{
350363
gl_Position = vec4(Position, 1);
351364
}";
352-
private const string fragmentHeaderCode = @"
353-
#version 450
354-
365+
public const string fragmentHeaderCode = @"#version 450
355366
layout(set = 0, binding = 0) uniform RuntimeData
356367
{
357368
vec4 mouse;
@@ -360,8 +371,6 @@ void main()
360371
float deltaTime;
361372
int frame;
362373
};
363-
364374
layout(location = 0) out vec4 out_Color;";
365-
366375
}
367376
}

YALCT/ShaderEditor.cs

+203-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2-
using System.IO;
2+
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Numerics;
5+
using System.Text.RegularExpressions;
46
using ImGuiNET;
57
using Veldrid;
68

@@ -11,14 +13,24 @@ public class ShaderEditor : IImGuiComponent
1113
{
1214
public const int MAXEDITORSTRINGLENGTH = 1000000; // man this is shitty tho
1315
private const float AUTOAPPLYINTERVAL = 1f;
16+
private const float FPSUPDATEINTERVAL = 0.25f;
1417
private const float HIDEUIHELPTEXTDURATION = 5f;
1518

1619
private bool showUI = true;
1720
private float hideUIHelpTextDelta = 0;
1821
private bool autoApply = true;
1922
private float autoApplyCurrentInterval = 0;
2023

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>();
2234

2335
private string fragmentCode = @"// Available inputs
2436
// mouse (vec4) : x,y => position, z => mouse 1 down, z => mouse 2 down
@@ -33,6 +45,7 @@ void main()
3345
float y = gl_FragCoord.y / resolution.y;
3446
out_Color = vec4(0,x,y,1);
3547
}";
48+
private readonly List<string> fragmentCodeLines = new List<string>();
3649

3750
public ImGuiController Controller { get; private set; }
3851
public string FragmentCode => fragmentCode;
@@ -44,6 +57,7 @@ public ShaderEditor(ImGuiController controller)
4457

4558
public void Initialize()
4659
{
60+
SplitLines();
4761
Apply();
4862
}
4963

@@ -132,43 +146,195 @@ private void SubmitMainMenu(float deltaTime)
132146
{
133147
autoApplyCurrentInterval = 0;
134148
}
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+
}
137155
Vector2 fpsSize = ImGui.CalcTextSize(fps);
138156
ImGui.SameLine(ImGui.GetWindowWidth() - fpsSize.X - 20);
139157
ImGui.Text(fps);
140158
ImGui.EndMainMenuBar();
141159
}
142160
}
143161

144-
private void SubmitEditorWindow()
162+
private unsafe void SubmitEditorWindow()
145163
{
146164
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
147165
ImGui.SetNextWindowSizeConstraints(Vector2.One * 500, Vector2.One * Controller.Context.Width);
148166
if (ImGui.Begin("Shader Editor"))
149167
{
150168
ImGui.PushFont(Controller.EditorFont);
151-
Vector2 editorWindowSize = ImGui.GetWindowSize();
152-
float bottomMargin = 40;
153-
if (errorMessage != null)
169+
if (ImGui.BeginTabBar("editor mode"))
154170
{
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();
160195
}
161-
ImGui.InputTextMultiline("",
162-
ref fragmentCode,
163-
MAXEDITORSTRINGLENGTH,
164-
new Vector2(editorWindowSize.X - 15, editorWindowSize.Y - bottomMargin),
165-
ImGuiInputTextFlags.AllowTabInput);
166196
ImGui.PopFont();
167197
ImGui.End();
168198
}
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+
}
169213
ImGui.PopStyleVar();
170214
}
171215

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+
172338
public void Update(float deltaTime)
173339
{
174340
if (autoApply)
@@ -184,17 +350,35 @@ public void Update(float deltaTime)
184350

185351
public void SetError(string error)
186352
{
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;
188363
}
189364

190365
public void Apply()
191366
{
367+
if (basicMode)
368+
{
369+
SplitLines();
370+
}
371+
else
372+
{
373+
MergeLines();
374+
}
192375
Controller.Context.CreateDynamicResources(fragmentCode);
193376
}
194377

195378
public void LoadShader(string shaderContent)
196379
{
197380
fragmentCode = shaderContent;
381+
SplitLines();
198382
Apply();
199383
}
200384
}

0 commit comments

Comments
 (0)