diff --git a/.gitmodules b/.gitmodules index 14e05b4..6140576 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "GlitchyEngineHelper/vendor/xxHash"] path = GlitchyEngineHelper/vendor/xxHash url = https://github.com/Cyan4973/xxHash.git +[submodule "GlitchyEngine/vendor/bon"] + path = GlitchyEngine/vendor/bon + url = https://github.com/EinScott/bon.git diff --git a/BeefSpace.toml b/BeefSpace.toml index d81985f..e243759 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,6 +1,6 @@ FileVersion = 1 -Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}} -WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper"], Dependencies = ["cgltf-beef", "DirectX", "DirectXTK", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef"]} +Projects = {Sandbox = {Path = "Sandbox"}, GlitchyEngine = {Path = "GlitchyEngine"}, GlitchLog = {Path = "GlitchLog"}, DirectX = {Path = "GlitchyEngine/vendor/directx/DirectX"}, DirectXTK = {Path = "GlitchyEngine/vendor/DirectXTK/DirectXTK-beef"}, LodePng = {Path = "vendor/lodepng-beef/lodepng-beef"}, FreeType = {Path = "GlitchyEngine/vendor/freetype"}, cgltf-beef = {Path = "GlitchyEngine/vendor/gltf/cgltf-beef"}, GlitchyEditor = {Path = "GlitchyEditor"}, msdfgen-beef = {Path = "GlitchyEngine/vendor/msdfgen/msdfgen-beef"}, ImGui = {Path = "GlitchyEngine/vendor/imgui/ImGui"}, ImGuiImplDX11 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplDX11"}, ImGuiImplWin32 = {Path = "GlitchyEngine/vendor/imgui/ImGuiImplWin32"}, ImGuizmo = {Path = "GlitchyEngine/vendor/imgui/ImGuizmo"}, GlitchyEngineHelper = {Path = "GlitchyEngineHelper"}, bon = {Path = "GlitchyEngine/vendor/bon"}} +WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngineHelper"], "GlitchyEngine/Dependencies" = ["cgltf-beef", "DirectX", "DirectXTK", "FreeType", "ImGui", "ImGuiImplDX11", "ImGuiImplWin32", "ImGuizmo", "LodePng", "msdfgen-beef", "bon"]} [Workspace] StartupProject = "GlitchyEditor" diff --git a/GlitchyEditor/content/Fonts/CascadiaCode.ttf b/GlitchyEditor/content/Fonts/CascadiaCode.ttf new file mode 100644 index 0000000..b47bf63 Binary files /dev/null and b/GlitchyEditor/content/Fonts/CascadiaCode.ttf differ diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 5e5974c..20a2b29 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -27,6 +27,8 @@ namespace GlitchyEditor RenderTarget2D _viewportTarget ~ _?.ReleaseRef(); + SettingsWindow _settingsWindow = new .() ~ delete _; + Entity _cameraEntity; Entity _otherCameraEntity; @@ -206,6 +208,8 @@ namespace GlitchyEditor _editor.Update(); + _settingsWindow.Show(); + return false; } @@ -213,8 +217,11 @@ namespace GlitchyEditor { ImGui.BeginMainMenuBar(); - if(ImGui.BeginMenu("File", false)) + if(ImGui.BeginMenu("File", true)) { + if (ImGui.MenuItem("Settings")) + _settingsWindow.Open = true; + ImGui.EndMenu(); } diff --git a/GlitchyEditor/src/SettingsWindow.bf b/GlitchyEditor/src/SettingsWindow.bf new file mode 100644 index 0000000..657be5f --- /dev/null +++ b/GlitchyEditor/src/SettingsWindow.bf @@ -0,0 +1,248 @@ +using GlitchyEditor.EditWindows; +using ImGui; +using GlitchyEngine; +using System; +using System.Collections; +using System.Reflection; + +namespace GlitchyEditor +{ + class SettingsWindow : EditorWindow + { + class Binding + { + public Object SettingsObject; + public String Name ~ delete _; + public String FieldName ~ delete _; + + public this(StringView name, StringView fieldName, Object settingsObject) + { + Name = new String(name); + FieldName = new String(fieldName); + SettingsObject = settingsObject; + } + } + + class Category + { + public List _bindings = new .() ~ DeleteContainerAndItems!(_); + + public String Header ~ delete _; + + public Object SettingsObject; + + public this(String header) + { + Header = new String(header); + } + + public void AddSetting(StringView name, StringView fieldName, Object settingsObject = null) + { + Binding binding = new .(name, fieldName, settingsObject ?? SettingsObject); + _bindings.Add(binding); + } + } + + Settings _settings; + + bool _settingsChanged = false; + + private Dictionary _categories = new .() ~ DeleteDictionaryAndValues!(_); + + public this() + { + _settings = Application.Get().Settings; + + Create(); + } + + void Create() + { + ScanForSettings(_settings); + + /*for (ISettings settings in _settings.[Friend]_userSettings) + { + ScanForSettings(settings); + }*/ + } + + Category AddCategory(String header) + { + if (_categories.TryGetValue(header, let category)) + return category; + + Category newCat = new Category(header); + _categories.Add(newCat.Header, newCat); + + return newCat; + } + + void ScanForSettings(Object container) + { + Type type = container.GetType(); + + for (var field in type.GetFields()) + { + Result settingResult = field.GetCustomAttribute(); + + if (settingResult case .Ok(let settingInfo)) + { + AddSetting(settingInfo.Category, settingInfo.Name, field.Name, container); + } + + Result containerResult = field.GetCustomAttribute(); + + if (containerResult case .Ok(let containerInfo)) + { + var res = field.GetValue(container, let childContainer); + + if (res case .Err(let error)) + { + Log.EngineLogger.Error($"Failed to get settings container. Error: {error}"); + + continue; + } + + ScanForSettings(childContainer); + } + } + } + + void AddSetting(String categoryName, String name, StringView fieldName, Object container) + { + Category category = AddCategory(categoryName); + + category.AddSetting(name, fieldName, container); + } + + protected override void InternalShow() + { + // Leave room for 1 line below us + ImGui.BeginChild("item view", ImGui.Vec2(0, -ImGui.GetFrameHeightWithSpacing())); + + ImGui.BeginTabBar("##Tabs"); + + for (Category category in _categories.Values) + { + if (ImGui.BeginTabItem(category.Header)) + { + ImGui.Columns(2); + defer ImGui.Columns(1); + ImGui.SetColumnWidth(0, 100); + + for (Binding setting in category._bindings) + { + ImGui.TextUnformatted(setting.Name); + + ImGui.NextColumn(); + + Type settingsObjectType = setting.SettingsObject.GetType(); + + Result result = settingsObjectType.GetField(setting.FieldName); + + if (result case .Err) + { + ImGui.PushStyleColor(.Text, ImGui.Vec4(1f, 0f, 0f, 1f)); + ImGui.Text($"Field {setting.FieldName} not found."); + ImGui.PopStyleColor(); + + continue; + } + + FieldInfo fieldInfo = result.Get(); + + Type fieldType = fieldInfo.FieldType; + + mixin GetSettingValue() + { + var error = fieldInfo.GetValue(setting.SettingsObject, var value); + + if (error case .Err(let err)) + { + Log.EngineLogger.Error($"Could not get value of setting {setting.FieldName}. Error: {err}"); + + ImGui.PushStyleColor(.Text, ImGui.Vec4(1f, 0f, 0f, 1f)); + ImGui.Text($"Could not get value of setting {setting.FieldName}. Error: {err}"); + ImGui.PopStyleColor(); + + break; + } + + value + } + + mixin SetSettingValue(T value) + { + var error = fieldInfo.SetValue(setting.SettingsObject, value); + + if (error case .Err(let err)) + { + Log.EngineLogger.Error($"Could not set value of setting {setting.FieldName}. Error: {err}"); + } + } + + switch (fieldType) + { + case typeof(int32): + int32 value = GetSettingValue!(); + + if (!ImGui.InputInt(scope $"##{setting.Name}", &value)) + break; + + SetSettingValue!(value); + + _settingsChanged = true; + case typeof(String): + String value = GetSettingValue!(); + + char8[256] buffer = .(); + + value.CopyTo(buffer); + + if (ImGui.InputText(scope $"##{setting.Name}", &buffer, buffer.Count)) + { + value..Clear().Append(&buffer); + + _settingsChanged = true; + } + } + + ImGui.NextColumn(); + } + + ImGui.EndTabItem(); + } + } + + ImGui.EndTabBar(); + + ImGui.EndChild(); + + ImGui.BeginDisabled(!_settingsChanged); + + if (ImGui.Button("Save")) + { + _settings.Save(); + _settings.Apply(); + _open = false; + } + + ImGui.SameLine(); + + if (ImGui.Button("Apply")) + { + _settings.Apply(); + } + + ImGui.EndDisabled(); + + ImGui.SameLine(); + + if (ImGui.Button("Cancel")) + { + Settings.Load(); + _open = false; + } + } + } +} \ No newline at end of file diff --git a/GlitchyEngine/BeefProj.toml b/GlitchyEngine/BeefProj.toml index bce6d77..46fdcda 100644 --- a/GlitchyEngine/BeefProj.toml +++ b/GlitchyEngine/BeefProj.toml @@ -1,5 +1,5 @@ FileVersion = 1 -Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", DirectXTK = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*"} +Dependencies = {GlitchLog = "*", corlib = "*", DirectX = "*", DirectXTK = "*", FreeType = "*", cgltf-beef = "*", msdfgen-beef = "*", ImGui = "*", ImGuiImplDX11 = "*", ImGuiImplWin32 = "*", ImGuizmo = "*", Beefy2D = "*", LodePng = "*", GlitchyEngineHelper = "*", bon = "*"} [Project] Name = "GlitchyEngine" diff --git a/GlitchyEngine/src/Application.bf b/GlitchyEngine/src/Application.bf index af6200a..577ddbb 100644 --- a/GlitchyEngine/src/Application.bf +++ b/GlitchyEngine/src/Application.bf @@ -40,6 +40,8 @@ namespace GlitchyEngine [Inline] public static Application Get() => s_Instance; + public Settings Settings {get; private set;} = new .() ~ delete _; + public this() { Profiler.ProfileFunction!(); @@ -70,6 +72,9 @@ namespace GlitchyEngine _imGuiLayer = new ImGuiLayer(); PushOverlay(_imGuiLayer); #endif + + GlitchyEngine.Settings.Load(); + Settings.Apply(); } public ~this() diff --git a/GlitchyEngine/src/Content/ContentManager.bf b/GlitchyEngine/src/Content/ContentManager.bf index 789abd7..624d4dd 100644 --- a/GlitchyEngine/src/Content/ContentManager.bf +++ b/GlitchyEngine/src/Content/ContentManager.bf @@ -25,6 +25,8 @@ namespace GlitchyEngine.Content interface IContentManager { + void GetFilePath(String outFilename, String filename); + Stream GetFile(String filename); } @@ -41,10 +43,15 @@ namespace GlitchyEngine.Content Runtime.Assert(Directory.Exists(contentRoot), "Content root directory doesn't exist."); } + public void GetFilePath(String outFilename, String filename) + { + Path.InternalCombine(outFilename, _contentRoot, filename); + } + public Stream GetFile(String filename) { String fullpath = scope .(_contentRoot.Length + 1 + filename.Length); - Path.InternalCombine(fullpath, _contentRoot, filename); + GetFilePath(fullpath, filename); FileStream stream = new FileStream(); var result = stream.Open(fullpath, .Read, .Read); diff --git a/GlitchyEngine/src/Extension/System/Runtime.bf b/GlitchyEngine/src/Extension/System/Runtime.bf new file mode 100644 index 0000000..ab2953c --- /dev/null +++ b/GlitchyEngine/src/Extension/System/Runtime.bf @@ -0,0 +1,13 @@ +namespace System +{ + extension Runtime + { + [NoReturn, Warn("The method is not implemented.")] +#unwarn + public static void NotImplemented(String filePath = Compiler.CallerFilePath, int line = Compiler.CallerLineNum) + { + String failStr = scope .()..AppendF("Not Implemented at line {} in {}", line, filePath); + Internal.FatalError(failStr, 1); + } + } +} \ No newline at end of file diff --git a/GlitchyEngine/src/ImGui/ImGuiLayer.bf b/GlitchyEngine/src/ImGui/ImGuiLayer.bf index f6644e3..0cb853c 100644 --- a/GlitchyEngine/src/ImGui/ImGuiLayer.bf +++ b/GlitchyEngine/src/ImGui/ImGuiLayer.bf @@ -2,13 +2,14 @@ using System; using ImGui; using GlitchyEngine.Events; using ImGuizmo; +using GlitchyEngine.Renderer; +using System.IO; using internal ImGui; #if GE_GRAPHICS_DX11 using GlitchyEngine.Platform.DX11; -using GlitchyEngine.Renderer; using internal GlitchyEngine.Platform.DX11; #endif @@ -19,6 +20,8 @@ namespace GlitchyEngine.ImGui { ImGui.IO* _io; + public bool SettingsInvalid; + public this() : base("ImGuiLayer") { } public override void OnAttach() @@ -87,6 +90,30 @@ namespace GlitchyEngine.ImGui { Debug.Profiler.ProfileFunction!(); + if (SettingsInvalid) + { + var settings = Application.Get().Settings.ImGuiSettings; + + ImGui.GetIO().Fonts.Clear(); + + String fullpath = scope String(); + Application.Get().ContentManager.GetFilePath(fullpath, settings.FontName); + if (File.Exists(fullpath)) + { + ImGui.GetIO().Fonts.AddFontFromFileTTF(fullpath, settings.FontSize); + } + else + { + ImGui.GetIO().Fonts.AddFontDefault(); + } + +#if GE_GRAPHICS_DX11 + ImGuiImplDX11.CreateDeviceObjects(); +#endif + + SettingsInvalid = false; + } + Begin(); { diff --git a/GlitchyEngine/src/Settings.bf b/GlitchyEngine/src/Settings.bf new file mode 100644 index 0000000..30825d9 --- /dev/null +++ b/GlitchyEngine/src/Settings.bf @@ -0,0 +1,121 @@ +using System; +using ImGui; +using System.Collections; +using System.IO; +using Bon; + +namespace GlitchyEngine +{ + interface ISettings + { + void Apply(); + } + + /// Fields with this Attribute will be scanned for Settings. + [AttributeUsage(.Field, .ReflectAttribute)] + struct SettingContainerAttribute : Attribute + { + } + + /// Fields with this Attribute will be exposed as settings. + [AttributeUsage(.Field, .ReflectAttribute)] + struct SettingAttribute : Attribute + { + public String Category; + public String Name; + + public this(String category, String name) + { + Category = category; + Name = name; + } + } + + [Reflect, BonTarget] + class Settings + { +#if IMGUI + [SettingContainer, BonInclude] + public readonly ImGuiSettings ImGuiSettings = new .() ~ delete _; +#endif + + /* + [BonInclude] + private List _userSettings ~ ClearAndDeleteItems!(_); + */ + + [AllowAppend] + public this() + { + /*List userSettings = append .(); + _userSettings = userSettings;*/ + } + + public void Apply() + { +#if IMGUI + ImGuiSettings.Apply(); +#endif + + /*for (let settings in _userSettings) + { + settings.Apply(); + }*/ + } + + public static void Load() + { + Settings settings = Application.Get().Settings; + + var result = Bon.DeserializeFromFile(ref settings, "./settings.bon"); + + if (result case .Err) + Log.EngineLogger.Error("Failed to deserialze settings."); + + Application.Get().Settings.Apply(); + } + + public void Save() + { + gBonEnv.serializeFlags |= .Verbose; + Bon.SerializeIntoFile(this, "./settings.bon"); + } + + /* + /// Registers a instance of a settings interface. Note: Takes ownership of the instance. + public void RegisterUserSettings(ISettings settings) + { + _userSettings.Add(settings); + } + + public T GetUserSettings() where T : ISettings, class + { + for (let v in _userSettings) + { + if (v is T) + { + return (T)v; + } + } + + return null; + }*/ + } + +#if IMGUI + [Reflect] + class ImGuiSettings + { + [Setting("UI", "Font Size"), BonInclude] + public int32 FontSize = 16; + + [Setting("UI", "Font name"), BonInclude] + public readonly String FontName = new .("Fonts/CascadiaCode.ttf") ~ delete _; + + public void Apply() + { + Application.Get().[Friend]_imGuiLayer.SettingsInvalid = true; + } + } +#endif +} \ No newline at end of file diff --git a/GlitchyEngine/vendor/bon b/GlitchyEngine/vendor/bon new file mode 160000 index 0000000..515b606 --- /dev/null +++ b/GlitchyEngine/vendor/bon @@ -0,0 +1 @@ +Subproject commit 515b606cf3048179b19d51e55a503b3007ae94e4