0
mirror of https://github.com/sampletext32/ParkanPlayground.git synced 2025-06-18 15:49:47 +03:00

create NResUI

This commit is contained in:
bird_egop
2024-11-15 19:06:44 +03:00
parent 0c39485188
commit 1091605e2d
22 changed files with 869 additions and 100 deletions

View File

@ -0,0 +1,7 @@
namespace NResUI.Abstractions
{
public interface IExitReceiver
{
void OnExit();
}
}

View File

@ -0,0 +1,7 @@
namespace NResUI.Abstractions
{
public interface IImGuiPanel
{
void OnImGuiRender();
}
}

View File

@ -0,0 +1,17 @@
using Silk.NET.Input;
namespace NResUI.Abstractions
{
public interface IKeyPressReceiver
{
void OnKeyPressed(Key key);
}
public interface IKeyReleaseReceiver
{
void OnKeyReleased(Key key);
}
public interface IKeyDownReceiver
{
void OnKeyDown(Key key);
}
}

View File

@ -0,0 +1,6 @@
namespace NResUI.Abstractions
{
public interface IService
{
}
}

View File

@ -0,0 +1,10 @@
namespace NResUI.Abstractions
{
public interface IUpdateReceiver
{
/// <summary>
/// Called before every UI render
/// </summary>
void OnUpdate(float delta);
}
}

140
NResUI/App.cs Normal file
View File

@ -0,0 +1,140 @@
using System.Numerics;
using ImGuiNET;
using Microsoft.Extensions.DependencyInjection;
using NResUI.Abstractions;
using NResUI.Models;
using Silk.NET.Input;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
namespace NResUI;
public class App
{
public GL GL { get; set; }
public IInputContext Input { get; set; }
public static App Instance;
private static bool _dockspaceOpen = true;
private static bool _optFullscreenPersistant = true;
private static bool _optFullscreen = _optFullscreenPersistant;
private static ImGuiDockNodeFlags _dockspaceFlags = ImGuiDockNodeFlags.None;
public ImFontPtr OpenSansFont;
private List<IImGuiPanel> _imGuiPanels;
public App()
{
Instance = this;
}
public void Init(IWindow window, GL openGl, ImFontPtr openSansFont)
{
ImGui.StyleColorsLight();
IServiceCollection serviceCollection = new ServiceCollection();
foreach (var type in Utils.GetAssignableTypes<IService>())
{
serviceCollection.AddSingleton(type);
}
foreach (var type in Utils.GetAssignableTypes<IImGuiPanel>())
{
serviceCollection.AddSingleton(type);
}
serviceCollection.AddSingleton(new ExplorerViewModel());
var serviceProvider = serviceCollection.BuildServiceProvider();
_imGuiPanels = Utils.GetAssignableTypes<IImGuiPanel>()
.Select(t => (serviceProvider.GetService(t) as IImGuiPanel)!)
.ToList();
}
public void OnImGuiRender()
{
ImGui.PushFont(OpenSansFont);
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
// because it would be confusing to have two docking targets within each others.
var windowFlags = ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking;
if (_optFullscreen)
{
var viewport = ImGui.GetMainViewport();
ImGui.SetNextWindowPos(viewport.Pos);
ImGui.SetNextWindowSize(viewport.Size);
ImGui.SetNextWindowViewport(viewport.ID);
ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0.0f);
ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0.0f);
windowFlags |= ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize |
ImGuiWindowFlags.NoMove;
windowFlags |= ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus;
}
// When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background and handle the pass-thru hole, so we ask Begin() to not render a background.
if ((_dockspaceFlags & ImGuiDockNodeFlags.PassthruCentralNode) != 0)
windowFlags |= ImGuiWindowFlags.NoBackground;
// Important: note that we proceed even if Begin() returns false (aka window is collapsed).
// This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
// all active windows docked into it will lose their parent and become undocked.
// We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
// any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0.0f, 0.0f));
ImGui.Begin("DockSpace Demo", ref _dockspaceOpen, windowFlags);
ImGui.PopStyleVar();
if (_optFullscreen)
ImGui.PopStyleVar(2);
// DockSpace
var io = ImGui.GetIO();
var style = ImGui.GetStyle();
var minWinSizeX = style.WindowMinSize.X;
style.WindowMinSize.X = 370.0f;
if ((io.ConfigFlags & ImGuiConfigFlags.DockingEnable) != 0)
{
var dockspaceId = ImGui.GetID("MyDockSpace");
ImGui.DockSpace(dockspaceId, new Vector2(0.0f, 0.0f), _dockspaceFlags);
}
style.WindowMinSize.X = minWinSizeX;
foreach (var imGuiPanel in _imGuiPanels)
{
imGuiPanel.OnImGuiRender();
}
ImGui.ShowMetricsWindow();
ImGui.ShowDemoWindow();
ImGui.PopFont();
ImGui.End();
}
public void Exit()
{
}
public void Update(double delta)
{
}
public void OnKeyPressed(Key key)
{
}
public void OnKeyDown(Key key)
{
}
public void OnKeyReleased(Key key)
{
}
}

View File

@ -0,0 +1,127 @@
using ImGuiNET;
using NResUI.Abstractions;
using NResUI.Models;
namespace NResUI.ImGuiUI;
public class ExplorerPanel : IImGuiPanel
{
private readonly ExplorerViewModel _viewModel;
public ExplorerPanel(ExplorerViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnImGuiRender()
{
if (ImGui.Begin("Explorer"))
{
if (!_viewModel.HasFile)
{
ImGui.Text("No NRes is opened");
}
else
{
if (_viewModel.Error != null)
{
ImGui.Text(_viewModel.Error);
}
if (_viewModel.Archive is not null)
{
ImGui.Text(_viewModel.Path);
ImGui.Text("Header: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.NRes);
ImGui.Text("Version: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.Version.ToString());
ImGui.Text("File Count: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.FileCount.ToString());
ImGui.Text("Total File Length: ");
ImGui.SameLine();
ImGui.Text(_viewModel.Archive.Header.TotalFileLengthBytes.ToString());
if (ImGui.BeginTable("content", 11))
{
ImGui.TableSetupColumn("Тип файла");
ImGui.TableSetupColumn("Magic1");
ImGui.TableSetupColumn("Длина файла в байтах");
ImGui.TableSetupColumn("Magic2");
ImGui.TableSetupColumn("Имя файла");
ImGui.TableSetupColumn("Magic3");
ImGui.TableSetupColumn("Magic4");
ImGui.TableSetupColumn("Magic5");
ImGui.TableSetupColumn("Magic6");
ImGui.TableSetupColumn("Смещение в байтах");
ImGui.TableSetupColumn("Индекс в файле");
ImGui.TableHeadersRow();
for (int i = 0; i < _viewModel.Archive.Files.Count; i++)
{
ImGui.TableNextRow();
ImGui.TableNextColumn();
ImGui.Text(_viewModel.Archive.Files[i].FileType);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic1.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.FileLength.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic2.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(_viewModel.Archive.Files[i].FileName);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic3.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic4.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic5.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Magic6.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.OffsetInFile.ToString()
);
ImGui.TableNextColumn();
ImGui.Text(
_viewModel.Archive.Files[i]
.Index.ToString()
);
}
ImGui.EndTable();
}
}
}
ImGui.End();
}
}
}

View File

@ -0,0 +1,39 @@
using System.Numerics;
using ImGuiNET;
using NResUI.Abstractions;
namespace NResUI.ImGuiUI;
public abstract class ImGuiModalPanel : IImGuiPanel
{
protected abstract string ImGuiId { get; }
private bool _shouldOpen = false;
public virtual void Open()
{
_shouldOpen = true;
}
protected abstract void OnImGuiRenderContent();
public void OnImGuiRender()
{
// this is a ImGui stack fix. Because menubars and some other controls use their separate stack context,
// The panel gets rendered on it's own, at the root of the stack, and with _shouldOpen we control, if the panel should open this frame.
if (_shouldOpen)
{
ImGui.OpenPopup(ImGuiId, ImGuiPopupFlags.AnyPopupLevel);
_shouldOpen = false;
}
ImGui.SetNextWindowSize(new Vector2(600, 400));
if (ImGui.BeginPopup(ImGuiId, ImGuiWindowFlags.NoResize))
{
OnImGuiRenderContent();
ImGui.EndPopup();
}
}
}

View File

@ -0,0 +1,108 @@
using System.Numerics;
using ImGuiNET;
using NativeFileDialogSharp;
using NResLib;
using NResUI.Abstractions;
using NResUI.Models;
namespace NResUI.ImGuiUI
{
public class MainMenuBar : IImGuiPanel
{
private readonly ExplorerViewModel _explorerViewModel;
public MainMenuBar(ExplorerViewModel explorerViewModel)
{
_explorerViewModel = explorerViewModel;
}
public void OnImGuiRender()
{
if (ImGui.BeginMenuBar())
{
if (ImGui.BeginMenu("File"))
{
if (ImGui.MenuItem("Open NRes"))
{
var result = Dialog.FileOpen();
if (result.IsOk)
{
var path = result.Path;
var parseResult = NResParser.ReadFile(path);
_explorerViewModel.SetParseResult(parseResult, path);
}
}
if (_explorerViewModel.HasFile)
{
if (ImGui.MenuItem("Экспортировать"))
{
var result = Dialog.FolderPicker();
if (result.IsOk)
{
var path = result.Path;
Console.WriteLine(path);
}
}
}
if (ImGui.BeginMenu("Open Recent"))
{
ImGui.EndMenu();
}
if (ImGui.MenuItem("Exit"))
{
App.Instance.Exit();
}
ImGui.EndMenu();
}
if (ImGui.BeginMenu("Windows"))
{
if (ImGui.MenuItem("Settings"))
{
}
ImGui.EndMenu();
}
ImGui.EndMenuBar();
}
}
// This is a direct port of imgui_demo.cpp HelpMarker function
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L190
private void ShowHint(string message)
{
// ImGui.TextDisabled("(?)");
if (ImGui.IsItemHovered())
{
// Change background transparency
ImGui.PushStyleColor(
ImGuiCol.PopupBg,
new Vector4(
1,
1,
1,
0.8f
)
);
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f);
ImGui.TextUnformatted(message);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
ImGui.PopStyleColor();
}
}
}
}

View File

@ -0,0 +1,26 @@
using NResLib;
namespace NResUI.Models;
public class ExplorerViewModel
{
public bool HasFile { get; set; }
public string? Error { get; set; }
public NResArchive? Archive { get; set; }
public string? Path { get; set; }
public void SetParseResult(NResParseResult result, string path)
{
Error = result.Error;
if (result.Archive != null)
{
HasFile = true;
}
Archive = result.Archive;
Path = path;
}
}

25
NResUI/NResUI.csproj Normal file
View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="assets\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="NativeFileDialogSharp" Version="0.5.0" />
<PackageReference Include="Silk.NET" Version="2.22.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.22.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NResLib\NResLib.csproj" />
</ItemGroup>
</Project>

109
NResUI/Program.cs Normal file
View File

@ -0,0 +1,109 @@
// See https://aka.ms/new-console-template for more information
using System.Drawing;
using ImGuiNET;
using NResUI;
using Silk.NET.Input;
using Silk.NET.OpenGL;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
var window = Window.Create(WindowOptions.Default);
// Declare some variables
ImGuiController controller = null!;
GL gl = null!;
IInputContext inputContext = null!;
var app = new App();
// Our loading function
window.Load += () =>
{
var openGl = window.CreateOpenGL();
ImFontPtr mainFont = null;
controller = new ImGuiController(
gl = openGl, // load OpenGL
window, // pass in our window
inputContext = window.CreateInput(), // create an input context
() =>
{
var io = ImGui.GetIO();
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
mainFont = io.Fonts.AddFontFromFileTTF(
filename: "assets/Font/OpenSans-Regular.ttf",
size_pixels: 18,
font_cfg: null,
glyph_ranges: io.Fonts.GetGlyphRangesCyrillic()
);
}
);
app.Init(window, openGl, mainFont);
inputContext.Keyboards[0]
.KeyDown += (keyboard, key, scancode) => { app.OnKeyDown(key); };
inputContext.Keyboards[0]
.KeyUp += (keyboard, key, scancode) => { app.OnKeyPressed(key); };
inputContext.Keyboards[0]
.KeyUp += (keyboard, key, scancode) => { app.OnKeyReleased(key); };
};
// Handle resizes
window.FramebufferResize += s =>
{
// Adjust the viewport to the new window size
gl.Viewport(s);
};
// Handles the dile drop and receives the array of paths to the files.
window.FileDrop += paths => { };
window.Update += delta =>
{
// Make sure ImGui is up-to-date
controller.Update((float) delta);
app.Update(delta);
};
// The render function
window.Render += delta =>
{
// This is where you'll do any rendering beneath the ImGui context
// Here, we just have a blank screen.
gl.ClearColor(
Color.FromArgb(
255,
(int) (.45f * 255),
(int) (.55f * 255),
(int) (.60f * 255)
)
);
gl.Clear((uint) ClearBufferMask.ColorBufferBit);
app.OnImGuiRender();
// Make sure ImGui renders too!
controller.Render();
};
// The closing function
window.Closing += () =>
{
app.Exit();
ImGui.SaveIniSettingsToDisk("imgui.ini");
// Dispose our controller first
controller?.Dispose();
// Dispose the input context
inputContext?.Dispose();
// Unload OpenGL
gl?.Dispose();
};
// Now that everything's defined, let's run this bad boy!
window.Run();

64
NResUI/Utils.cs Normal file
View File

@ -0,0 +1,64 @@
using System.Reflection;
namespace NResUI
{
public static class Utils
{
public static bool IsDirectory(this FileSystemInfo info)
{
// get the file attributes for file or directory
FileAttributes attr = info.Attributes;
//detect whether its a directory or file
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
return true;
else
return false;
}
public static bool IsDirectoryPath(this string path)
{
return Directory.Exists(path);
}
public static void ClearContent(this DirectoryInfo directoryInfo)
{
foreach (var directory in directoryInfo.EnumerateDirectories())
{
directory.Delete(true);
}
foreach (var file in directoryInfo.EnumerateFiles())
{
file.Delete();
}
}
public static IEnumerable<Type> GetAssignableTypesFromAssembly<T>(Assembly assembly)
{
return assembly.ExportedTypes
.Where(t => t.IsAssignableTo(typeof(T)) && t is {IsAbstract: false, IsInterface: false});
}
public static IList<Type> GetAssignableTypes<T>()
{
var executingAssembly = Assembly.GetExecutingAssembly();
var referencedAssemblyNames = executingAssembly.GetReferencedAssemblies();
var types = referencedAssemblyNames.SelectMany(
name =>
GetAssignableTypesFromAssembly<T>(Assembly.Load(name))
)
.Concat(
GetAssignableTypesFromAssembly<T>(executingAssembly)
)
.ToList();
return types;
}
public static bool IsNullOrEmpty(this string? str)
{
return string.IsNullOrEmpty(str);
}
}
}

Binary file not shown.