├── .gitattributes ├── .gitignore ├── AndroidTests └── AndroidTests.csproj ├── Hexa.NET.ImGui.Widgets.Extras ├── Globals.cs ├── Hexa.NET.ImGui.Widgets.Extras.csproj ├── ImGuiBezierWidget.cs ├── ImGuiCurveEditor.cs └── TextEditor │ ├── Breakpoint.cs │ ├── CaseInsensitiveComparer.cs │ ├── ColorRGBA.cs │ ├── CursorState.cs │ ├── Highlight │ └── CSharp │ │ └── CSharpSyntaxHighlight.cs │ ├── ImGui.cs │ ├── JumpTarget.cs │ ├── LineIndexSpan.cs │ ├── NewLineType.cs │ ├── Panels │ └── ExplorerSidePanel.cs │ ├── SearchResult.cs │ ├── SidePanel.cs │ ├── SyntaxHighlight.cs │ ├── SyntaxHighlightDefaults.cs │ ├── SyntaxHighlightDefinition.cs │ ├── TextDrawData.cs │ ├── TextEditOp.cs │ ├── TextEditor.cs │ ├── TextEditorTab.cs │ ├── TextEditorWindow.cs │ ├── TextHighlightSpan.cs │ ├── TextHighlightSpanStartComparer.cs │ ├── TextHistory.cs │ ├── TextHistoryEntry.cs │ ├── TextSelection.cs │ ├── TextSource.cs │ ├── TextSpan.cs │ └── TextSpanStartComparer.cs ├── Hexa.NET.ImGui.Widgets.sln ├── Hexa.NET.ImGui.Widgets ├── ComboHelper.cs ├── Dialogs │ ├── AscendingComparer.cs │ ├── CompareByDateModifiedComparer.cs │ ├── CompareByNameComparer.cs │ ├── CompareBySizeComparer.cs │ ├── CompareByTypeComparer.cs │ ├── Dialog.cs │ ├── DialogManager.cs │ ├── DialogMessageBox.cs │ ├── DialogResult.cs │ ├── FileDialogBase.cs │ ├── FileIconHelper.cs │ ├── FileSystemHelper.cs │ ├── FileSystemItem.cs │ ├── IDialog.cs │ ├── IFileSystemItem.cs │ ├── IPopup.cs │ ├── ImGuiFileView.cs │ ├── Modal.cs │ ├── MultiSelection.cs │ ├── OpenFileDialog.cs │ ├── OpenFolderDialog.cs │ ├── PathValidator.cs │ ├── PopupManager.cs │ ├── RenameFileDialog.cs │ ├── SaveFileDialog.cs │ ├── SearchFilterDate.cs │ ├── SearchFilterSize.cs │ ├── SearchOptions.cs │ └── SearchOptionsFlags.cs ├── Extensions │ ├── EnumExtension.cs │ ├── ListExtensions.cs │ └── SpanHelper.cs ├── Globals.cs ├── Hexa.NET.ImGui.Widgets.csproj ├── IImGuiWindow.cs ├── IUIElement.cs ├── ImGuiAnimationHelper.cs ├── ImGuiBreadcrumb.cs ├── ImGuiButton.cs ├── ImGuiFileTreeView.cs ├── ImGuiGC.cs ├── ImGuiName.cs ├── ImGuiProgressBar.cs ├── ImGuiSpinner.cs ├── ImGuiSplitter.cs ├── ImGuiTreeNode.cs ├── ImGuiWidgetFlameGraph.cs ├── ImWindow.cs ├── ImageHelper.cs ├── Imaging │ └── ImageHelper.cs ├── MaterialIcons.cs ├── MessageBox.cs ├── MessageBoxResult.cs ├── MessageBoxType.cs ├── MessageBoxes.cs ├── Text │ └── StrBuilderExtensions.cs ├── TextHelper.cs ├── TooltipHelper.cs ├── TryUtils.cs ├── WidgetManager.cs ├── WidgetStyle.cs └── assets │ └── fileTypes.json ├── LICENSE.txt ├── PerformanceTests ├── PerformanceTests.csproj └── Program.cs ├── README.md ├── TestApp ├── ImGuiManager.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── ResizedEventArgs.cs ├── SDLNativeContext.cs ├── TestApp.csproj ├── Time.cs ├── WidgetDemo.cs ├── assets │ └── fonts │ │ ├── MaterialSymbolsRounded.ttf │ │ ├── arial.ttf │ │ └── arialuni.TTF └── test.txt └── TextEditor ├── ImGuiManager.cs ├── Program.cs ├── ResizedEventArgs.cs ├── SDLNativeContext.cs ├── TextEditor.csproj ├── Time.cs ├── WidgetDemo.cs └── assets └── fonts ├── MaterialSymbolsRounded.ttf ├── arial.ttf └── arialuni.ttf /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | # JetBrains Rider 366 | .idea/ 367 | -------------------------------------------------------------------------------- /AndroidTests/AndroidTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0-android 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/Globals.cs: -------------------------------------------------------------------------------- 1 | global using static Hexa.NET.Utilities.Utils; -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/Hexa.NET.ImGui.Widgets.Extras.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0;net9.0-android;net8.0;net8.0-android 5 | enable 6 | enable 7 | true 8 | 9 | 1.0.6 10 | 1.0.6 11 | 12 | Hexa.NET.ImGui.Widgets is a comprehensive library of custom widgets for the ImGui graphical user interface library. This package includes a variety of pre-built widgets that enhance the functionality and usability of ImGui in your .NET applications. Each widget is designed to be easy to integrate, with consistent styling and behavior. This library is an extension of the Hexa.NET.ImGui wrapper, providing additional UI components for a seamless user experience. 13 | 14 | 15 | Hexa.NET, ImGui, GUI, Widgets, UI, User Interface, .NET, C#, Custom Widgets, HexaNET, ImGuiWrapper 16 | 17 | Juna Meinhold 18 | Copyright (c) 2024 Juna Meinhold 19 | https://github.com/HexaEngine/Hexa.NET.ImGui 20 | https://github.com/HexaEngine/Hexa.NET.ImGui 21 | git 22 | LICENSE.txt 23 | README.md 24 | 25 | true 26 | $(NoWarn);1591 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/Breakpoint.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using System; 4 | 5 | public class BreakpointComparer : IComparer 6 | { 7 | public static readonly BreakpointComparer Instance = new(); 8 | 9 | public int Compare(Breakpoint x, Breakpoint y) 10 | { 11 | return x.Line.CompareTo(y.Line); 12 | } 13 | } 14 | 15 | public struct Breakpoint : IEquatable 16 | { 17 | public int Line; 18 | public bool Enabled; 19 | 20 | public Breakpoint(int line, bool enabled) 21 | { 22 | Line = line; 23 | Enabled = enabled; 24 | } 25 | 26 | public static readonly Breakpoint Invalid = new(-1, false); 27 | 28 | public override readonly bool Equals(object? obj) 29 | { 30 | return obj is Breakpoint breakpoint && Equals(breakpoint); 31 | } 32 | 33 | public readonly bool Equals(Breakpoint other) 34 | { 35 | return Line == other.Line; 36 | } 37 | 38 | public override readonly int GetHashCode() 39 | { 40 | return HashCode.Combine(Line); 41 | } 42 | 43 | public static bool operator ==(Breakpoint left, Breakpoint right) 44 | { 45 | return left.Equals(right); 46 | } 47 | 48 | public static bool operator !=(Breakpoint left, Breakpoint right) 49 | { 50 | return !(left == right); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/CaseInsensitiveComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using System.Collections.Generic; 4 | 5 | public readonly struct CaseInsensitiveComparer : IEqualityComparer 6 | { 7 | public static readonly CaseInsensitiveComparer Default = new(); 8 | 9 | public readonly bool Equals(char x, char y) 10 | { 11 | return char.ToLowerInvariant(x) == char.ToLowerInvariant(y); 12 | } 13 | 14 | public readonly int GetHashCode(char obj) 15 | { 16 | return char.ToLowerInvariant(obj).GetHashCode(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/ColorRGBA.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using System.Numerics; 4 | using System.Xml.Serialization; 5 | 6 | [XmlRoot("Color")] 7 | public struct ColorRGBA 8 | { 9 | [XmlAttribute("r")] 10 | public float R; 11 | 12 | [XmlAttribute("g")] 13 | public float G; 14 | 15 | [XmlAttribute("b")] 16 | public float B; 17 | 18 | [XmlAttribute("a")] 19 | public float A; 20 | 21 | public ColorRGBA(float r, float g, float b, float a) 22 | { 23 | R = r; 24 | G = g; 25 | B = b; 26 | A = a; 27 | } 28 | 29 | public ColorRGBA(Vector4 color) 30 | { 31 | R = color.X; 32 | G = color.Y; 33 | B = color.Z; 34 | A = color.W; 35 | } 36 | 37 | public ColorRGBA(uint color) 38 | { 39 | R = (color >> 24 & 0xff) / (float)byte.MaxValue; 40 | G = (color >> 16 & 0xff) / (float)byte.MaxValue; 41 | B = (color >> 8 & 0xff) / (float)byte.MaxValue; 42 | A = (color & 0xff) / (float)byte.MaxValue; 43 | } 44 | 45 | public static implicit operator Vector4(ColorRGBA c) 46 | { 47 | return new(c.R, c.G, c.B, c.A); 48 | } 49 | 50 | public static implicit operator ColorRGBA(uint color) 51 | { 52 | return new(color); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/CursorState.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | public struct CursorState 4 | { 5 | public int Index; 6 | public int Line; 7 | public int Column; 8 | 9 | public CursorState(int index, int line, int column) 10 | { 11 | Index = index; 12 | Line = line; 13 | Column = column; 14 | } 15 | 16 | public static readonly CursorState NewLineLF = new(1, 1, 0); 17 | public static readonly CursorState NewLineCR = new(1, 1, 0); 18 | public static readonly CursorState NewLineCRLF = new(2, 1, 0); 19 | public static readonly CursorState Invalid = new(-1, -1, -1); 20 | 21 | public static CursorState FromOffset(int offset) 22 | { 23 | return new CursorState(offset, 0, offset); 24 | } 25 | 26 | public static CursorState FromIndex(int index, TextSource source) 27 | { 28 | int line = 0; 29 | int column = 0; 30 | for (; line < source.LineCount; line++) 31 | { 32 | var lineSpan = source.Lines[line]; 33 | if (lineSpan.Start <= index && lineSpan.End >= index) 34 | { 35 | column = index - lineSpan.Start; 36 | break; 37 | } 38 | } 39 | 40 | return new(index, line, column); 41 | } 42 | 43 | public static CursorState FromLineColumn(int line, int column, TextSource source) 44 | { 45 | var lineSpan = source.Lines[line]; 46 | var index = lineSpan.Start + column; 47 | return new(index, line, column); 48 | } 49 | 50 | public static CursorState operator ++(CursorState state) 51 | { 52 | int newIndex = state.Index + 1; 53 | int newColumn = state.Column + 1; 54 | return new(newIndex, state.Line, newColumn); 55 | } 56 | 57 | public static CursorState operator --(CursorState state) 58 | { 59 | int newLine = state.Line; 60 | int newColumn = state.Column - 1; 61 | if (newColumn < 0) 62 | { 63 | newLine--; 64 | newColumn = 0; 65 | } 66 | return new(state.Index - 1, newLine, newColumn); 67 | } 68 | 69 | public static CursorState operator +(CursorState a, CursorState b) 70 | { 71 | return new CursorState(a.Index + b.Index, a.Line + b.Line, a.Column + b.Column); 72 | } 73 | 74 | public static CursorState operator -(CursorState a, CursorState b) 75 | { 76 | return new CursorState(a.Index - b.Index, a.Line - b.Line, a.Column - b.Column); 77 | } 78 | 79 | public static implicit operator int(CursorState state) => state.Index; 80 | } 81 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/ImGui.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Text; 3 | 4 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 5 | { 6 | public static partial class ImGuiWChar 7 | { 8 | public const int StackallocLimit = 2048; 9 | 10 | public static unsafe Vector2 CalcTextSize(char* start, char* end) 11 | { 12 | int length = (int)(end - start); 13 | 14 | var byteCount = Encoding.UTF8.GetByteCount(new Span(start, length)); 15 | 16 | byte* ptr; 17 | if (byteCount + 1 > StackallocLimit) 18 | { 19 | ptr = AllocT(byteCount + 1); 20 | } 21 | else 22 | { 23 | byte* pPtr = stackalloc byte[byteCount + 1]; 24 | ptr = pPtr; 25 | } 26 | Encoding.UTF8.GetBytes(new Span(start, length), new Span(ptr, byteCount)); 27 | ptr[byteCount] = 0; 28 | 29 | Vector2 result = ImGui.CalcTextSize(ptr); 30 | 31 | if (byteCount + 1 > StackallocLimit) 32 | { 33 | Free(ptr); 34 | } 35 | 36 | return result; 37 | } 38 | 39 | public static unsafe void AddText(ImDrawList* drawList, Vector2 pos, uint col, char* textBegin, char* textEnd) 40 | { 41 | int length = (int)(textEnd - textBegin); 42 | 43 | var byteCount = Encoding.UTF8.GetByteCount(new Span(textBegin, length)); 44 | 45 | byte* ptr; 46 | if (byteCount + 1 > StackallocLimit) 47 | { 48 | ptr = AllocT(byteCount + 1); 49 | } 50 | else 51 | { 52 | byte* pPtr = stackalloc byte[byteCount + 1]; 53 | ptr = pPtr; 54 | } 55 | Encoding.UTF8.GetBytes(new Span(textBegin, length), new Span(ptr, byteCount)); 56 | ptr[byteCount] = 0; 57 | 58 | ImGui.AddText(drawList, pos, col, ptr); 59 | 60 | if (byteCount + 1 > StackallocLimit) 61 | { 62 | Free(ptr); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/JumpTarget.cs: -------------------------------------------------------------------------------- 1 | using Hexa.NET.ImGui; 2 | 3 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 4 | { 5 | public struct JumpTarget 6 | { 7 | public int Index; 8 | public int Length; 9 | public int Line; 10 | public int Column; 11 | public ImGuiScrollFlags Flags; 12 | 13 | public JumpTarget(int index, int line, int column, ImGuiScrollFlags flags) 14 | { 15 | Index = index; 16 | Line = line; 17 | Column = column; 18 | Flags = flags; 19 | } 20 | 21 | public JumpTarget(int index, int length, int line, int column, ImGuiScrollFlags flags) 22 | { 23 | Index = index; 24 | Length = length; 25 | Line = line; 26 | Column = column; 27 | 28 | Flags = flags; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/LineIndexSpan.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | public readonly struct LineIndexSpan : IEquatable 4 | { 5 | public readonly int Start; 6 | public readonly int End; 7 | 8 | public LineIndexSpan(int start, int end) 9 | { 10 | Start = start; 11 | End = end; 12 | } 13 | 14 | public readonly int Length => End - Start; 15 | 16 | public readonly void Deconstruct(out int startSpanIndex, out int endSpanIndex) 17 | { 18 | startSpanIndex = Start; 19 | endSpanIndex = End; 20 | } 21 | 22 | public override bool Equals(object? obj) 23 | { 24 | return obj is LineIndexSpan span && Equals(span); 25 | } 26 | 27 | public bool Equals(LineIndexSpan other) 28 | { 29 | return Start == other.Start && 30 | End == other.End; 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | return HashCode.Combine(Start, End); 36 | } 37 | 38 | public static bool operator ==(LineIndexSpan left, LineIndexSpan right) 39 | { 40 | return left.Equals(right); 41 | } 42 | 43 | public static bool operator !=(LineIndexSpan left, LineIndexSpan right) 44 | { 45 | return !(left == right); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/NewLineType.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | public enum NewLineType 4 | { 5 | CRLF, 6 | LF, 7 | CR, 8 | Mixed 9 | } 10 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/SearchResult.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.Utilities; 4 | using System; 5 | 6 | public unsafe struct SearchResult 7 | { 8 | public StdWString* String; 9 | public int Start; 10 | public int End; 11 | public int Line; 12 | public int Column; 13 | 14 | public SearchResult(StdWString* @string, int start, int len, int line, int column) 15 | { 16 | String = @string; 17 | Start = start; 18 | End = start + len; 19 | Line = line; 20 | Column = column; 21 | } 22 | 23 | public static SearchResult FromIndex(StdWString* @string, int index, int len, TextSource source) 24 | { 25 | int line = 0; 26 | int column = 0; 27 | for (; line < source.LineCount; line++) 28 | { 29 | var lineSpan = source.Lines[line]; 30 | if (lineSpan.Start <= index && lineSpan.End >= index) 31 | { 32 | column = index - lineSpan.Start; 33 | break; 34 | } 35 | } 36 | 37 | return new(@string, index, len, line, column); 38 | } 39 | 40 | public readonly int Length => End - Start; 41 | 42 | public readonly char* Data => String->Data + Start; 43 | 44 | public readonly char* DataEnd => String->Data + End; 45 | 46 | public readonly ReadOnlySpan AsReadOnlySpan() 47 | { 48 | return new ReadOnlySpan(String->Data + Start, Length); 49 | } 50 | 51 | public readonly Span AsSpan() 52 | { 53 | return new Span(String->Data + Start, Length); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/SidePanel.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | public abstract class SidePanel : IDisposable 4 | { 5 | private bool disposedValue; 6 | 7 | public abstract string Icon { get; } 8 | 9 | public abstract string Title { get; } 10 | 11 | public void Draw() 12 | { 13 | DrawContent(); 14 | } 15 | 16 | public abstract void DrawContent(); 17 | 18 | protected virtual void DisposeCore() 19 | { 20 | } 21 | 22 | protected virtual void Dispose(bool disposing) 23 | { 24 | if (!disposedValue) 25 | { 26 | DisposeCore(); 27 | disposedValue = true; 28 | } 29 | } 30 | 31 | public void Dispose() 32 | { 33 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 34 | Dispose(disposing: true); 35 | GC.SuppressFinalize(this); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/SyntaxHighlightDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.ImGui.Widgets.Extras.TextEditor.Highlight.CSharp; 4 | 5 | public static class SyntaxHighlightDefaults 6 | { 7 | static SyntaxHighlightDefaults() 8 | { 9 | CSharp = new CSharpSyntaxHighlight(); 10 | } 11 | 12 | public static SyntaxHighlight Default { get; } = new("Default", ""); 13 | 14 | public static SyntaxHighlight CSharp { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/SyntaxHighlightDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | public class SyntaxHighlightDefinition(string name, string pattern, ColorRGBA color) 4 | { 5 | public string Name { get; set; } = name; 6 | 7 | public string Pattern { get; set; } = pattern; 8 | 9 | public ColorRGBA Color { get; set; } = color; 10 | } 11 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextDrawData.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public class TextDrawData 7 | { 8 | public readonly List Spans = []; 9 | public readonly List FoldSpans = []; 10 | public readonly List LineSpanIndices = []; 11 | 12 | public IEnumerable GetVisibleSpans(float scroll, float lineHeight, float maxHeight) 13 | { 14 | int start = (int)MathF.Floor(scroll / lineHeight); 15 | int end = (int)MathF.Ceiling(maxHeight / lineHeight) + start; 16 | end = Math.Min(end, LineSpanIndices.Count); 17 | 18 | for (int i = start; i < end; i++) 19 | { 20 | var (startSpanIndex, endSpanIndex) = LineSpanIndices[i]; 21 | 22 | for (int j = startSpanIndex; j <= endSpanIndex; j++) 23 | { 24 | yield return Spans[j]; 25 | } 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextEditOp.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | public enum TextEditOp 4 | { 5 | Unknown, 6 | Insert, 7 | Erase, 8 | Replace, 9 | Cut, 10 | Paste, 11 | } 12 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextHighlightSpan.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.Utilities; 5 | using System.Numerics; 6 | 7 | public unsafe struct TextHighlightSpan 8 | { 9 | public StdWString* String; 10 | public Vector2 Origin; 11 | public int Start; 12 | public int End; 13 | public float Size; 14 | public uint Color; 15 | public bool HasColor; 16 | public bool Control; 17 | 18 | public TextHighlightSpan(StdWString* str, Vector2 origin, ColorRGBA color, int start, int length, bool control = false) 19 | { 20 | String = str; 21 | Origin = origin; 22 | Start = start; 23 | End = start + length; 24 | Color = ImGui.ColorConvertFloat4ToU32(color); 25 | HasColor = true; 26 | Control = control; 27 | } 28 | 29 | public TextHighlightSpan(StdWString* str, Vector2 origin, ColorRGBA color, bool hasColor, int start, int length) 30 | { 31 | String = str; 32 | Origin = origin; 33 | Start = start; 34 | End = start + length; 35 | Color = ImGui.ColorConvertFloat4ToU32(color); 36 | HasColor = hasColor; 37 | } 38 | 39 | public TextHighlightSpan(StdWString* str, Vector2 origin, int start, int length) 40 | { 41 | String = str; 42 | Origin = origin; 43 | Start = start; 44 | End = start + length; 45 | HasColor = false; 46 | } 47 | 48 | public readonly int Length => End - Start; 49 | 50 | public readonly char* Data => String->Data + Start; 51 | 52 | public readonly ReadOnlySpan AsReadOnlySpan() 53 | { 54 | return new ReadOnlySpan(String->Data + Start, Length); 55 | } 56 | 57 | public readonly Span AsSpan() 58 | { 59 | return new Span(String->Data + Start, Length); 60 | } 61 | 62 | public override readonly string ToString() 63 | { 64 | return $"[{Start}-{End}] {Color:X}, {new string(Data, 0, Length)}"; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextHighlightSpanStartComparer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 3 | { 4 | public class TextHighlightSpanStartComparer : IComparer 5 | { 6 | public static readonly TextHighlightSpanStartComparer Instance = new(); 7 | 8 | public int Compare(TextHighlightSpan x, TextHighlightSpan y) 9 | { 10 | if (x.Start > y.Start) 11 | { 12 | return 1; 13 | } 14 | else if (x.Start < y.Start) 15 | { 16 | return -1; 17 | } 18 | else 19 | { 20 | return 0; 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextHistory.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.Utilities; 4 | 5 | public unsafe class TextHistory 6 | { 7 | private readonly TextHistoryEntry[] undoHistory; 8 | private readonly TextHistoryEntry[] redoHistory; 9 | private readonly TextSource source; 10 | private readonly int maxCount; 11 | private int undoHistoryCount; 12 | private int redoHistoryCount; 13 | private bool disposedValue; 14 | 15 | public TextHistory(TextSource source, int maxCount) 16 | { 17 | this.source = source; 18 | this.maxCount = maxCount; 19 | 20 | undoHistory = new TextHistoryEntry[maxCount]; 21 | redoHistory = new TextHistoryEntry[maxCount]; 22 | } 23 | 24 | public int UndoCount => undoHistoryCount; 25 | 26 | public int RedoCount => redoHistoryCount; 27 | 28 | public bool CanUndo => undoHistoryCount > 0; 29 | 30 | public bool CanRedo => redoHistoryCount > 0; 31 | 32 | public void Clear() 33 | { 34 | for (int i = 0; i < undoHistory.Length; i++) 35 | { 36 | undoHistory[i].Release(); 37 | } 38 | undoHistoryCount = 0; 39 | for (int i = 0; i < redoHistory.Length; i++) 40 | { 41 | redoHistory[i].Release(); 42 | } 43 | redoHistoryCount = 0; 44 | } 45 | 46 | public void UndoPush() 47 | { 48 | UndoPushInternal(); 49 | for (int i = 0; i < redoHistoryCount; i++) 50 | { 51 | var index = maxCount - redoHistoryCount; 52 | redoHistory[index].Release(); 53 | } 54 | redoHistoryCount = 0; 55 | } 56 | 57 | private void UndoPushInternal() 58 | { 59 | var last = undoHistory[^1]; 60 | 61 | // Release the last entry if it contains data 62 | last.Release(); 63 | 64 | // Shift entries in undoHistory to the right 65 | for (int i = undoHistory.Length - 1; i > 0; i--) 66 | { 67 | undoHistory[i] = undoHistory[i - 1]; 68 | } 69 | 70 | // Allocate a new StdString and clone the current text 71 | last.Data = AllocT(); 72 | *last.Data = source.Text->Clone(); 73 | 74 | // Place the new entry at the beginning of undoHistory 75 | undoHistory[0] = last; 76 | undoHistoryCount = Math.Min(undoHistoryCount + 1, maxCount); 77 | } 78 | 79 | private void RedoPushInternal(TextHistoryEntry entry) 80 | { 81 | var last = redoHistory[^1]; 82 | 83 | // Release the last entry if it contains data 84 | last.Release(); 85 | 86 | // Shift entries in redoHistory to the right 87 | for (int i = redoHistory.Length - 1; i > 0; i--) 88 | { 89 | redoHistory[i] = redoHistory[i - 1]; 90 | } 91 | 92 | // Allocate a new StdString and clone the entry data 93 | last.Data = AllocT(); 94 | *last.Data = entry.Data->Clone(); 95 | 96 | // Place the new entry at the beginning of redoHistory 97 | redoHistory[0] = last; 98 | redoHistoryCount = Math.Min(redoHistoryCount + 1, maxCount); 99 | } 100 | 101 | public void Undo() 102 | { 103 | if (undoHistoryCount == 0) 104 | { 105 | return; 106 | } 107 | 108 | var first = undoHistory[0]; 109 | 110 | // Shift entries in undoHistory to the left 111 | for (int i = 0; i < undoHistory.Length - 1; i++) 112 | { 113 | undoHistory[i] = undoHistory[i + 1]; 114 | } 115 | 116 | RedoPushInternal(first); 117 | 118 | // Set the source text to the first entry's data 119 | source.SetText(first.Data); 120 | 121 | // Place the released entry at the end of undoHistory 122 | undoHistory[^1] = first; 123 | undoHistoryCount--; 124 | } 125 | 126 | public void Redo() 127 | { 128 | if (redoHistoryCount == 0) 129 | { 130 | return; 131 | } 132 | 133 | var first = redoHistory[0]; 134 | 135 | // Shift entries in redoHistory to the left 136 | for (int i = 0; i < redoHistory.Length - 1; i++) 137 | { 138 | redoHistory[i] = redoHistory[i + 1]; 139 | } 140 | 141 | UndoPushInternal(); 142 | 143 | // Set the source text to the first entry's data 144 | source.SetText(first.Data); 145 | 146 | // Place the released entry at the end of redoHistory 147 | redoHistory[^1] = first; 148 | redoHistoryCount--; 149 | } 150 | 151 | public void Dispose() 152 | { 153 | if (disposedValue) 154 | { 155 | return; 156 | } 157 | 158 | Clear(); 159 | 160 | disposedValue = true; 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextHistoryEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.Utilities; 4 | 5 | public unsafe struct TextHistoryEntry 6 | { 7 | public StdWString* Data; 8 | 9 | public void Release() 10 | { 11 | if (Data != null) 12 | { 13 | Data->Release(); 14 | Free(Data); 15 | Data = null; 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextSelection.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.Utilities; 4 | using System; 5 | 6 | public unsafe struct TextSelection 7 | { 8 | public StdWString* Text; 9 | public CursorState Start; 10 | public CursorState End; 11 | 12 | public TextSelection() 13 | { 14 | Start = CursorState.Invalid; 15 | End = CursorState.Invalid; 16 | } 17 | 18 | public TextSelection(StdWString* text, CursorState start, CursorState end) 19 | { 20 | Text = text; 21 | Start = start; 22 | End = end; 23 | } 24 | 25 | public static readonly TextSelection Invalid = new(null, CursorState.Invalid, CursorState.Invalid); 26 | 27 | public char* Data => Text->Data + Math.Min(Start.Index, End.Index); 28 | 29 | public readonly int Length => Math.Abs(End - Start); 30 | 31 | public bool IsValid() 32 | { 33 | var start = Start.Index; 34 | var end = End.Index; 35 | if (start > end) 36 | { 37 | (start, end) = (end, start); 38 | } 39 | return start >= 0 && Text != null && end <= Text->Size; 40 | } 41 | 42 | public readonly CursorState EffectiveStart => Start.Index <= End.Index ? Start : End; 43 | 44 | public readonly CursorState EffectiveEnd => Start.Index <= End.Index ? End : Start; 45 | } 46 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextSource.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.Utilities; 4 | using System.Globalization; 5 | using System.Numerics; 6 | using System.Text; 7 | 8 | public unsafe class TextSource 9 | { 10 | private StdWString* text; 11 | private NewLineType newLineType; 12 | 13 | public TextSource(string text) 14 | { 15 | this.text = AllocT(); 16 | *this.text = new(text); 17 | IsBinary = !IsText(this.text); 18 | newLineType = GetNewLineType(this.text); 19 | LineCount = CountLines(this.text); 20 | } 21 | 22 | public StdWString* Text 23 | { 24 | get => text; 25 | } 26 | 27 | public Encoding Encoding { get; set; } = Encoding.UTF8; 28 | 29 | public NewLineType NewLineType 30 | { 31 | get => newLineType; 32 | set 33 | { 34 | newLineType = value; 35 | if (value == NewLineType.Mixed) 36 | { 37 | return; 38 | } 39 | ConvertNewLineType(text, value); 40 | LineCount = CountLines(text); 41 | } 42 | } 43 | 44 | public bool IsBinary { get; set; } 45 | 46 | public bool Changed { get; set; } 47 | 48 | public int LineCount { get; set; } 49 | 50 | public int CharacterCount { get; set; } 51 | 52 | public void SetText(StdWString* newText) 53 | { 54 | text->Resize(newText->Size); 55 | MemcpyT(newText->CStr(), text->CStr(), newText->Size); 56 | } 57 | 58 | public static bool IsText(StdWString* text) 59 | { 60 | for (int i = 0; i < text->Size; i++) 61 | { 62 | char c = (*text)[i]; 63 | if (char.GetUnicodeCategory(c) == UnicodeCategory.Control) 64 | { 65 | return false; 66 | } 67 | } 68 | 69 | return true; 70 | } 71 | 72 | public static NewLineType GetNewLineType(StdWString* text) 73 | { 74 | if (text->Size == 0) 75 | { 76 | return NewLineType.CRLF; 77 | } 78 | 79 | bool crlf = false; 80 | bool lf = false; 81 | bool cr = false; 82 | 83 | bool foundFlag = false; 84 | 85 | for (int i = 0; i < text->Size; i++) 86 | { 87 | char c = (*text)[i]; 88 | char c1 = (i + 1 < text->Size) ? (*text)[i + 1] : '\0'; 89 | 90 | if (c == '\r' && c1 == '\n') 91 | { 92 | if (foundFlag && !crlf) 93 | { 94 | return NewLineType.Mixed; 95 | } 96 | crlf = true; 97 | foundFlag = true; 98 | i++; 99 | } 100 | else if (c == '\n') 101 | { 102 | if (foundFlag && !lf) 103 | { 104 | return NewLineType.Mixed; 105 | } 106 | lf = true; 107 | foundFlag = true; 108 | } 109 | else if (c == '\r') 110 | { 111 | if (foundFlag && !cr) 112 | { 113 | return NewLineType.Mixed; 114 | } 115 | cr = true; 116 | foundFlag = true; 117 | } 118 | } 119 | 120 | if (crlf) 121 | { 122 | return NewLineType.CRLF; 123 | } 124 | if (lf) 125 | { 126 | return NewLineType.LF; 127 | } 128 | if (cr) 129 | { 130 | return NewLineType.CR; 131 | } 132 | 133 | return NewLineType.Mixed; 134 | } 135 | 136 | public List Lines { get; } = []; 137 | 138 | public float MaxLineWidth { get; private set; } 139 | 140 | public Vector2 LayoutSize { get; private set; } 141 | 142 | public void Update(float lineHeight) 143 | { 144 | char* pText = text->Data; 145 | Lines.Clear(); 146 | int lineStart = 0; 147 | float maxWidth = 0; 148 | 149 | for (int i = 0; i < text->Size; i++) 150 | { 151 | char c = pText[i]; 152 | 153 | if (c == '\n' || c == '\r') 154 | { 155 | // for CRLF 156 | if (c == '\r' && i < text->Size - 1 && pText[i + 1] == '\n') 157 | { 158 | i++; 159 | } 160 | 161 | TextSpan span = new(text, lineStart, i - lineStart); 162 | span.Size = ImGuiWChar.CalcTextSize(pText + lineStart, pText + i).X; 163 | maxWidth = Math.Max(maxWidth, span.Size); 164 | Lines.Add(span); 165 | lineStart = i + 1; 166 | } 167 | } 168 | 169 | if (lineStart <= text->Size) 170 | { 171 | TextSpan span = new(text, lineStart, text->Size - lineStart); 172 | span.Size = ImGuiWChar.CalcTextSize(pText + lineStart, pText + text->Size).X; 173 | maxWidth = Math.Max(maxWidth, span.Size); 174 | Lines.Add(span); 175 | } 176 | 177 | MaxLineWidth = maxWidth; 178 | LineCount = Lines.Count; 179 | LayoutSize = new(maxWidth, Lines.Count * lineHeight); 180 | } 181 | 182 | public static void ConvertNewLineType(StdWString* text, NewLineType newLineType) 183 | { 184 | // don't care just return original, you can't convert it to mixed anyway. 185 | if (newLineType == NewLineType.Mixed) 186 | { 187 | return; 188 | } 189 | 190 | string separator = newLineType switch 191 | { 192 | NewLineType.CRLF => "\r\n", 193 | NewLineType.LF => "\n", 194 | NewLineType.CR => "\r", 195 | _ => throw new NotSupportedException(), 196 | }; 197 | StringBuilder sb = new(); 198 | for (int i = 0; i < text->Size; i++) 199 | { 200 | char c = (*text)[i]; 201 | char c1 = (*text)[i + 1 == text->Size ? i : i + 1]; 202 | 203 | if (c == '\r' && c1 == '\n' || c == '\n' || c == '\r') 204 | { 205 | sb.Append(separator); 206 | if (c1 == '\n') 207 | { 208 | i++; 209 | } 210 | continue; 211 | } 212 | 213 | sb.Append(c); 214 | } 215 | return; 216 | } 217 | 218 | public static int CountLines(StdWString* text) 219 | { 220 | int lineCount = 1; 221 | 222 | foreach (char c in *text) 223 | { 224 | if (c == '\n') 225 | { 226 | lineCount++; 227 | } 228 | } 229 | 230 | return lineCount; 231 | } 232 | 233 | public void Dispose() 234 | { 235 | text->Release(); 236 | Free(text); 237 | text = null; 238 | } 239 | } 240 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextSpan.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 2 | { 3 | using Hexa.NET.Utilities; 4 | using System.Diagnostics; 5 | 6 | [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] 7 | public unsafe struct TextSpan : IEquatable 8 | { 9 | public StdWString* String; 10 | public int Start; 11 | public int End; 12 | public float Size; 13 | 14 | public TextSpan(StdWString* str, int start, int length) 15 | { 16 | String = str; 17 | Start = start; 18 | End = start + length; 19 | } 20 | 21 | public readonly int Length => End - Start; 22 | 23 | public readonly char* Data => String->Data + Start; 24 | 25 | public readonly char* DataEnd => String->Data + End; 26 | 27 | public readonly ReadOnlySpan AsReadOnlySpan() 28 | { 29 | return new ReadOnlySpan(String->Data + Start, Length); 30 | } 31 | 32 | public readonly Span AsSpan() 33 | { 34 | return new Span(String->Data + Start, Length); 35 | } 36 | 37 | public override readonly bool Equals(object? obj) 38 | { 39 | return obj is TextSpan span && Equals(span); 40 | } 41 | 42 | public readonly bool Equals(TextSpan other) 43 | { 44 | return Start == other.Start && 45 | End == other.End; 46 | } 47 | 48 | public override readonly int GetHashCode() 49 | { 50 | return HashCode.Combine(Start, End); 51 | } 52 | 53 | public static bool operator ==(TextSpan left, TextSpan right) 54 | { 55 | return left.Equals(right); 56 | } 57 | 58 | public static bool operator !=(TextSpan left, TextSpan right) 59 | { 60 | return !(left == right); 61 | } 62 | 63 | private readonly string GetDebuggerDisplay() 64 | { 65 | return $"{Start} .. {End} {new string(Data, 0, Length)}"; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.Extras/TextEditor/TextSpanStartComparer.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Hexa.NET.ImGui.Widgets.Extras.TextEditor 3 | { 4 | public class TextSpanStartComparer : IComparer 5 | { 6 | public static readonly TextSpanStartComparer Instance = new(); 7 | 8 | public int Compare(TextSpan x, TextSpan y) 9 | { 10 | if (x.Start > y.Start) 11 | { 12 | return 1; 13 | } 14 | else if (x.Start < y.Start) 15 | { 16 | return -1; 17 | } 18 | else 19 | { 20 | return 0; 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.35122.118 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hexa.NET.ImGui.Widgets", "Hexa.NET.ImGui.Widgets\Hexa.NET.ImGui.Widgets.csproj", "{18AA69A5-04E1-4E49-8CB8-378DF59CF980}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "TestApp\TestApp.csproj", "{088B7207-ED91-435C-8A35-9ED64A510F50}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hexa.NET.ImGui.Widgets.Extras", "Hexa.NET.ImGui.Widgets.Extras\Hexa.NET.ImGui.Widgets.Extras.csproj", "{A9F458A4-F994-43C3-944E-1E2E635F3A7D}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceTests", "PerformanceTests\PerformanceTests.csproj", "{B0B1BFA5-9CB4-4811-9167-A8B7566EAF41}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextEditor", "TextEditor\TextEditor.csproj", "{442A0F34-84DA-4696-BD2D-35E7CAE91B5D}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {18AA69A5-04E1-4E49-8CB8-378DF59CF980}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {18AA69A5-04E1-4E49-8CB8-378DF59CF980}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {18AA69A5-04E1-4E49-8CB8-378DF59CF980}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {18AA69A5-04E1-4E49-8CB8-378DF59CF980}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {088B7207-ED91-435C-8A35-9ED64A510F50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {088B7207-ED91-435C-8A35-9ED64A510F50}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {088B7207-ED91-435C-8A35-9ED64A510F50}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {088B7207-ED91-435C-8A35-9ED64A510F50}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {A9F458A4-F994-43C3-944E-1E2E635F3A7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {A9F458A4-F994-43C3-944E-1E2E635F3A7D}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {A9F458A4-F994-43C3-944E-1E2E635F3A7D}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {A9F458A4-F994-43C3-944E-1E2E635F3A7D}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {B0B1BFA5-9CB4-4811-9167-A8B7566EAF41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {B0B1BFA5-9CB4-4811-9167-A8B7566EAF41}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {B0B1BFA5-9CB4-4811-9167-A8B7566EAF41}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {B0B1BFA5-9CB4-4811-9167-A8B7566EAF41}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {442A0F34-84DA-4696-BD2D-35E7CAE91B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {442A0F34-84DA-4696-BD2D-35E7CAE91B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {442A0F34-84DA-4696-BD2D-35E7CAE91B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {442A0F34-84DA-4696-BD2D-35E7CAE91B5D}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {52FCE378-A076-497C-A15D-CA3BE20AD9FD} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ComboHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using System; 5 | using System.Linq; 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | /// 9 | /// A helper class for working with ImGui combo boxes to select enum values of a specified enum type. 10 | /// 11 | /// The enum type. 12 | public static class ComboEnumHelper where T : struct, Enum 13 | { 14 | #if NET5_0_OR_GREATER 15 | private static readonly T[] values = Enum.GetValues(); 16 | private static readonly string[] names = Enum.GetNames(); 17 | #else 18 | private static readonly T[] values = (T[])Enum.GetValues(typeof(T)); 19 | private static readonly string[] names = Enum.GetNames(typeof(T)); 20 | #endif 21 | 22 | /// 23 | /// Displays a combo box to select an enum value. 24 | /// 25 | /// The label for the combo box. 26 | /// The currently selected enum value (modified by user interaction). 27 | /// true if the user selects a new value, false otherwise. 28 | public static bool Combo(string label, ref T value) 29 | { 30 | int index = Array.IndexOf(values, value); 31 | if (ImGui.Combo(label, ref index, names, names.Length)) 32 | { 33 | value = values[index]; 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | /// 40 | /// Displays the text representation of an enum value. 41 | /// 42 | /// The enum value to display. 43 | public static void Text(T value) 44 | { 45 | int index = Array.IndexOf(values, value); 46 | ImGui.Text(names[index]); 47 | } 48 | 49 | public static string GetName(T value) 50 | { 51 | int index = Array.IndexOf(values, value); 52 | return names[index]; 53 | } 54 | } 55 | 56 | /// 57 | /// A helper class for working with ImGui combo boxes to select enum values of various enum types. 58 | /// 59 | public static class ComboEnumHelper 60 | { 61 | private static readonly Dictionary values = new(); 62 | private static readonly Dictionary names = new(); 63 | #if NET7_0_OR_GREATER 64 | 65 | [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "All members are included by [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)].")] 66 | #endif 67 | private static void Get( 68 | #if NET7_0_OR_GREATER 69 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] 70 | #endif 71 | Type type, out object[] values, out string[] names) 72 | { 73 | if (ComboEnumHelper.values.TryGetValue(type, out var objects)) 74 | { 75 | values = objects; 76 | names = ComboEnumHelper.names[type]; 77 | return; 78 | } 79 | 80 | values = Enum.GetValues(type).Cast().ToArray(); 81 | names = Enum.GetNames(type); 82 | ComboEnumHelper.values.Add(type, values); 83 | ComboEnumHelper.names.Add(type, names); 84 | } 85 | 86 | /// 87 | /// Displays a combo box to select an enum value of a specified enum type. 88 | /// 89 | /// The label for the combo box. 90 | /// The enum type to select values from. 91 | /// The currently selected enum value (modified by user interaction). 92 | /// true if the user selects a new value, false otherwise. 93 | public static bool Combo(string label, 94 | #if NET7_0_OR_GREATER 95 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] 96 | #endif 97 | Type type, ref object value) 98 | { 99 | Get(type, out var values, out var names); 100 | int index = Array.IndexOf(values, value); 101 | if (ImGui.Combo(label, ref index, names, names.Length)) 102 | { 103 | value = values[index]; 104 | return true; 105 | } 106 | return false; 107 | } 108 | 109 | /// 110 | /// Displays the text representation of an enum value of a specified enum type. 111 | /// 112 | /// The enum type to select values from. 113 | /// The enum value to display. 114 | public static void Text( 115 | #if NET7_0_OR_GREATER 116 | [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] 117 | #endif 118 | Type type, object value) 119 | { 120 | Get(type, out var values, out var names); 121 | int index = Array.IndexOf(values, value); 122 | ImGui.Text(names[index]); 123 | } 124 | } 125 | 126 | public static class ComboTextHelper 127 | { 128 | public static bool Combo(string label, string[] values, ref string value) 129 | { 130 | int index = Array.IndexOf(values, value); 131 | if (ImGui.Combo(label, ref index, values, values.Length)) 132 | { 133 | value = values[index]; 134 | return true; 135 | } 136 | return false; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/AscendingComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public readonly struct AscendingComparer : IComparer where TComparer : struct, IComparer where T : struct 4 | { 5 | private readonly TComparer comparer = new(); 6 | 7 | public AscendingComparer() 8 | { 9 | } 10 | 11 | public int Compare(T x, T y) 12 | { 13 | return -comparer.Compare(x, y); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/CompareByDateModifiedComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public readonly struct CompareByDateModifiedComparer : IComparer where T : struct, IFileSystemItem 4 | { 5 | public int Compare(T a, T b) 6 | { 7 | int cmp = BaseComparer.CompareByBase(a, b); 8 | if (cmp != 0) 9 | { 10 | return cmp; 11 | } 12 | cmp = a.DateModified.CompareTo(b.DateModified); 13 | if (cmp != 0) 14 | { 15 | return cmp; 16 | } 17 | return string.Compare(a.Name, b.Name, StringComparison.Ordinal); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/CompareByNameComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public readonly struct CompareByNameComparer : IComparer where T : struct, IFileSystemItem 4 | { 5 | public int Compare(T a, T b) 6 | { 7 | int cmp = BaseComparer.CompareByBase(a, b); 8 | if (cmp != 0) 9 | { 10 | return cmp; 11 | } 12 | return string.Compare(a.Name, b.Name, StringComparison.Ordinal); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/CompareBySizeComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public readonly struct CompareBySizeComparer : IComparer where T : struct, IFileSystemItem 4 | { 5 | public int Compare(T a, T b) 6 | { 7 | int cmp = BaseComparer.CompareByBase(a, b); 8 | if (cmp != 0) 9 | { 10 | return cmp; 11 | } 12 | cmp = a.Size.CompareTo(b.Size); 13 | if (cmp != 0) 14 | { 15 | return cmp; 16 | } 17 | return string.Compare(a.Name, b.Name, StringComparison.Ordinal); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/CompareByTypeComparer.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public readonly struct CompareByTypeComparer : IComparer where T : struct, IFileSystemItem 4 | { 5 | public int Compare(T a, T b) 6 | { 7 | int cmp = BaseComparer.CompareByBase(a, b); 8 | if (cmp != 0) 9 | { 10 | return cmp; 11 | } 12 | cmp = string.Compare(a.Type, b.Type, StringComparison.Ordinal); 13 | if (cmp != 0) 14 | { 15 | return cmp; 16 | } 17 | return string.Compare(a.Name, b.Name, StringComparison.Ordinal); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/Dialog.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Numerics; 5 | using System.Runtime.CompilerServices; 6 | 7 | public delegate void DialogCallback(object? sender, DialogResult result); 8 | 9 | [Flags] 10 | public enum DialogFlags 11 | { 12 | None = 1 << 0, 13 | CenterOnParent = 1 << 1, 14 | AlwaysCenter = 1 << 2 15 | } 16 | 17 | public abstract class Dialog : IDialog 18 | { 19 | private bool windowEnded; 20 | private bool shown; 21 | protected DialogCallback? callback; 22 | private Vector2 position; 23 | private Vector2 size; 24 | private uint viewportId; 25 | private IUIElement? parent; 26 | private DialogFlags flags; 27 | private bool firstFrame = true; 28 | 29 | public Vector2 InitialSize { get; set; } = new(1000, 600); 30 | 31 | public DialogResult Result { get; protected set; } 32 | 33 | public abstract string Name { get; } 34 | 35 | protected abstract ImGuiWindowFlags Flags { get; } 36 | 37 | public Vector2 Position => position; 38 | 39 | public Vector2 Size => size; 40 | 41 | public uint ViewportId => viewportId; 42 | 43 | public event SizeChangedEventHandler? SizeChanged; 44 | 45 | public event PositionChangedEventHandler? PositionChanged; 46 | 47 | public event ViewportChangedEventHandler? ViewportChanged; 48 | 49 | public bool Shown => shown; 50 | 51 | public object? Userdata { get; set; } 52 | 53 | public unsafe void Draw(ImGuiWindowFlags overwriteFlags) 54 | { 55 | if (!shown) return; 56 | 57 | var windowFlags = Flags | overwriteFlags; 58 | 59 | var alwaysCenter = (flags & DialogFlags.AlwaysCenter) != 0; 60 | var viewportsEnable = (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0; 61 | if ((firstFrame || alwaysCenter) && (flags & DialogFlags.CenterOnParent) != 0) 62 | { 63 | Vector2 center = ImGui.GetIO().DisplaySize * 0.5f; 64 | if (parent != null) 65 | { 66 | center = parent.Position + parent.Size * 0.5f; 67 | if (viewportsEnable) 68 | { 69 | ImGui.SetNextWindowViewport(parent.ViewportId); 70 | } 71 | } 72 | 73 | ImGui.SetNextWindowPos(center, alwaysCenter ? ImGuiCond.Always : ImGuiCond.Appearing, new(0.5f)); 74 | } 75 | 76 | bool wasOpen = shown; 77 | if (!ImGui.Begin(Name, ref shown, windowFlags)) 78 | { 79 | if (wasOpen && wasOpen != shown) 80 | { 81 | Close(); 82 | } 83 | ImGui.End(); 84 | return; 85 | } 86 | 87 | if (wasOpen && wasOpen != shown) 88 | { 89 | Close(); 90 | ImGui.End(); 91 | return; 92 | } 93 | 94 | if (viewportsEnable) 95 | { 96 | var currentViewport = ImGui.GetWindowViewport().ID; 97 | 98 | if (viewportId != currentViewport) 99 | { 100 | var oldViewportId = viewportId; 101 | viewportId = currentViewport; 102 | OnViewportChangedInternal(oldViewportId, viewportId); 103 | } 104 | } 105 | 106 | var currentSize = ImGui.GetWindowSize(); 107 | 108 | if (size != currentSize) 109 | { 110 | var oldSize = size; 111 | size = currentSize; 112 | OnSizeChangedInternal(oldSize, size); 113 | } 114 | 115 | var currentPosition = ImGui.GetWindowPos(); 116 | 117 | if (position != currentPosition) 118 | { 119 | var oldPosition = position; 120 | position = currentPosition; 121 | OnPositionChangedInternal(oldPosition, position); 122 | } 123 | 124 | DrawContent(); 125 | 126 | windowEnded = false; 127 | 128 | if (firstFrame) 129 | { 130 | ImGui.SetWindowSize(InitialSize); 131 | firstFrame = false; 132 | } 133 | 134 | if (!windowEnded) 135 | { 136 | ImGui.End(); 137 | } 138 | } 139 | 140 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 141 | protected void EndDraw() 142 | { 143 | ImGui.End(); 144 | windowEnded = true; 145 | } 146 | 147 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 148 | protected abstract void DrawContent(); 149 | 150 | private void OnPositionChangedInternal(Vector2 oldPosition, Vector2 position) 151 | { 152 | OnPositionChanged(oldPosition, position); 153 | PositionChanged?.Invoke(this, oldPosition, position); 154 | } 155 | 156 | protected virtual void OnPositionChanged(Vector2 oldPosition, Vector2 position) 157 | { 158 | } 159 | 160 | private void OnSizeChangedInternal(Vector2 oldSize, Vector2 size) 161 | { 162 | OnSizeChanged(oldSize, size); 163 | SizeChanged?.Invoke(this, oldSize, size); 164 | } 165 | 166 | protected virtual void OnSizeChanged(Vector2 oldSize, Vector2 size) 167 | { 168 | } 169 | 170 | private void OnViewportChangedInternal(uint oldViewportId, uint viewportId) 171 | { 172 | OnViewportChanged(oldViewportId, viewportId); 173 | ViewportChanged?.Invoke(this, oldViewportId, viewportId); 174 | } 175 | 176 | protected virtual void OnViewportChanged(uint oldViewportId, uint viewportId) 177 | { 178 | } 179 | 180 | public virtual void Close() 181 | { 182 | shown = false; 183 | DialogManager.CloseDialog(this); 184 | callback?.Invoke(this, Result); 185 | callback = null; // clear callback afterwards to prevent memory leaks 186 | } 187 | 188 | protected virtual void Close(DialogResult result) 189 | { 190 | Result = result; 191 | Close(); 192 | } 193 | 194 | public virtual void Reset() 195 | { 196 | Result = DialogResult.None; 197 | } 198 | 199 | public virtual void Show() 200 | { 201 | DialogManager.ShowDialog(this); 202 | shown = true; 203 | } 204 | 205 | public virtual void Show(DialogCallback callback) 206 | { 207 | this.callback = callback; 208 | Show(); 209 | } 210 | 211 | public virtual void Show(DialogCallback callback, IUIElement parent, DialogFlags flags) 212 | { 213 | this.flags = flags; 214 | this.parent = parent; 215 | this.callback = callback; 216 | Show(); 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/DialogManager.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.ImGui.Widgets; 5 | 6 | public static class DialogManager 7 | { 8 | private static readonly List dialogs = []; 9 | private static readonly Queue closing = []; 10 | private static readonly object _lock = new(); 11 | 12 | public static void ShowDialog(IDialog dialog) 13 | { 14 | lock (_lock) 15 | { 16 | dialogs.Add(dialog); 17 | } 18 | } 19 | 20 | public static void CloseDialog(IDialog dialog) 21 | { 22 | lock (_lock) 23 | { 24 | closing.Enqueue(dialog); 25 | } 26 | } 27 | 28 | public static void Draw() 29 | { 30 | lock (_lock) 31 | { 32 | for (int i = 0; i < dialogs.Count; i++) 33 | { 34 | bool isNotLast = i != dialogs.Count - 1; 35 | 36 | ImGui.BeginDisabled(isNotLast); 37 | ImGuiWindowFlags overwriteFlags = ImGuiWindowFlags.None; 38 | if (isNotLast) 39 | { 40 | overwriteFlags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoMouseInputs | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoBringToFrontOnFocus; 41 | } 42 | 43 | dialogs[i].Draw(overwriteFlags); 44 | 45 | ImGui.EndDisabled(); 46 | } 47 | 48 | #if NETSTANDARD2_0 49 | 50 | while (closing.Count > 0) 51 | { 52 | var dialog = closing.Dequeue(); 53 | dialogs.Remove(dialog); 54 | } 55 | #else 56 | while (closing.TryDequeue(out var dialog)) 57 | { 58 | dialogs.Remove(dialog); 59 | } 60 | #endif 61 | 62 | WidgetManager.BlockInput = dialogs.Count > 0; 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/DialogMessageBox.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | 5 | public enum DialogMessageBoxType 6 | { 7 | Ok, 8 | OkCancel, 9 | YesNo, 10 | YesNoCancel, 11 | YesCancel, 12 | } 13 | 14 | public class DialogMessageBox : Dialog 15 | { 16 | private readonly string title; 17 | private readonly string message; 18 | private readonly DialogMessageBoxType type; 19 | 20 | public DialogMessageBox(string title, string message, DialogMessageBoxType type) 21 | { 22 | this.title = title; 23 | this.message = message; 24 | this.type = type; 25 | } 26 | 27 | public override string Name => title; 28 | 29 | protected override ImGuiWindowFlags Flags { get; } = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings; 30 | 31 | protected override void DrawContent() 32 | { 33 | ImGui.Text(message); 34 | switch (type) 35 | { 36 | case DialogMessageBoxType.Ok: 37 | if (ImGui.Button("Ok")) 38 | { 39 | Close(DialogResult.Ok); 40 | } 41 | break; 42 | 43 | case DialogMessageBoxType.OkCancel: 44 | if (ImGui.Button("Ok")) 45 | { 46 | Close(DialogResult.Ok); 47 | } 48 | ImGui.SameLine(); 49 | if (ImGui.Button("Cancel")) 50 | { 51 | Close(DialogResult.Cancel); 52 | } 53 | break; 54 | 55 | case DialogMessageBoxType.YesNo: 56 | if (ImGui.Button("Yes")) 57 | { 58 | Close(DialogResult.Yes); 59 | } 60 | ImGui.SameLine(); 61 | if (ImGui.Button("No")) 62 | { 63 | Close(DialogResult.No); 64 | } 65 | break; 66 | 67 | case DialogMessageBoxType.YesNoCancel: 68 | if (ImGui.Button("Yes")) 69 | { 70 | Close(DialogResult.Yes); 71 | } 72 | ImGui.SameLine(); 73 | if (ImGui.Button("No")) 74 | { 75 | Close(DialogResult.No); 76 | } 77 | ImGui.SameLine(); 78 | if (ImGui.Button("Cancel")) 79 | { 80 | Close(DialogResult.Cancel); 81 | } 82 | break; 83 | 84 | case DialogMessageBoxType.YesCancel: 85 | if (ImGui.Button("Yes")) 86 | { 87 | Close(DialogResult.Yes); 88 | } 89 | ImGui.SameLine(); 90 | if (ImGui.Button("Cancel")) 91 | { 92 | Close(DialogResult.Cancel); 93 | } 94 | break; 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/DialogResult.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public enum DialogResult 4 | { 5 | None = -1, 6 | Ok = 0, 7 | Cancel = 1, 8 | Failed = 2, 9 | 10 | Yes = 0, 11 | No = 3 12 | } 13 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/IDialog.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public interface IDialog : IUIElement 4 | { 5 | bool Shown { get; } 6 | 7 | void Draw(ImGuiWindowFlags overwriteFlags); 8 | 9 | void Close(); 10 | 11 | void Reset(); 12 | 13 | void Show(); 14 | } 15 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/IFileSystemItem.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public interface IFileSystemItem 4 | { 5 | string Path { get; } 6 | 7 | string Icon { get; } 8 | 9 | string Name { get; } 10 | 11 | FileSystemItemFlags Flags { get; } 12 | 13 | DateTime DateModified { get; } 14 | 15 | string Type { get; } 16 | 17 | long Size { get; } 18 | 19 | CommonFilePermissions Permissions { get; } 20 | 21 | #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER 22 | 23 | public bool IsFile => (Flags & FileSystemItemFlags.Folder) == 0; 24 | 25 | public bool IsFolder => (Flags & FileSystemItemFlags.Folder) != 0; 26 | 27 | public bool IsHidden => (Flags & FileSystemItemFlags.Hidden) != 0; 28 | 29 | #else 30 | public bool IsFile { get; } 31 | 32 | public bool IsFolder { get; } 33 | 34 | public bool IsHidden { get; } 35 | #endif 36 | } 37 | 38 | public static class BaseComparer 39 | { 40 | public static int CompareByBase(IFileSystemItem a, IFileSystemItem b) 41 | { 42 | if (a.IsFolder && !b.IsFolder) 43 | { 44 | return -1; 45 | } 46 | if (!a.IsFolder && b.IsFolder) 47 | { 48 | return 1; 49 | } 50 | return 0; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/IPopup.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public interface IPopup 4 | { 5 | string Name { get; } 6 | 7 | bool Shown { get; } 8 | 9 | void Draw(); 10 | 11 | void Reset(); 12 | 13 | internal void Close(bool internalValue); 14 | 15 | void Close(); 16 | 17 | internal void Show(bool internalValue); 18 | 19 | void Show(); 20 | } 21 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/Modal.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Runtime.CompilerServices; 5 | 6 | public abstract class Modal : IPopup 7 | { 8 | private bool windowEnded; 9 | private bool signalShow; 10 | protected bool signalClose; 11 | protected bool shown; 12 | 13 | public abstract string Name { get; } 14 | 15 | protected abstract ImGuiWindowFlags Flags { get; } 16 | 17 | public bool Shown { get => shown; protected set => shown = value; } 18 | 19 | public virtual unsafe void Draw() 20 | { 21 | if (!shown) 22 | { 23 | return; 24 | } 25 | 26 | if (signalShow) 27 | { 28 | shown = true; 29 | ImGui.OpenPopup(Name, ImGuiPopupFlags.None); 30 | signalShow = false; 31 | } 32 | 33 | if (!ImGui.BeginPopupModal(Name, ref shown, Flags)) 34 | { 35 | return; 36 | } 37 | 38 | if (signalClose) 39 | { 40 | ImGui.CloseCurrentPopup(); 41 | signalClose = false; 42 | shown = false; 43 | ImGui.EndPopup(); 44 | return; 45 | } 46 | windowEnded = false; 47 | 48 | DrawContent(); 49 | 50 | if (!windowEnded) 51 | { 52 | ImGui.EndPopup(); 53 | } 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | protected void EndDraw() 58 | { 59 | ImGui.EndPopup(); 60 | windowEnded = true; 61 | } 62 | 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | public abstract void DrawContent(); 65 | 66 | public abstract void Reset(); 67 | 68 | public virtual void Show() 69 | { 70 | PopupManager.Add(this, true); 71 | } 72 | 73 | public virtual void Close() 74 | { 75 | signalClose = true; 76 | } 77 | 78 | void IPopup.Close(bool internalValue) 79 | { 80 | signalClose = true; 81 | } 82 | 83 | void IPopup.Show(bool internalValue) 84 | { 85 | signalShow = true; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/OpenFolderDialog.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | 5 | public class OpenFolderDialog : FileDialogBase 6 | { 7 | private readonly MultiSelection selection = []; 8 | 9 | public OpenFolderDialog() 10 | { 11 | string startingPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 12 | if (File.Exists(startingPath)) 13 | { 14 | startingPath = Path.GetDirectoryName(startingPath) ?? string.Empty; 15 | } 16 | else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) 17 | { 18 | startingPath = Environment.CurrentDirectory; 19 | if (string.IsNullOrEmpty(startingPath)) 20 | { 21 | startingPath = AppContext.BaseDirectory; 22 | } 23 | } 24 | 25 | RootFolder = startingPath; 26 | SetInternal(startingPath, refresh: false); 27 | OnlyAllowFolders = true; 28 | } 29 | 30 | public OpenFolderDialog(string startingPath) 31 | { 32 | if (File.Exists(startingPath)) 33 | { 34 | startingPath = Path.GetDirectoryName(startingPath) ?? string.Empty; 35 | } 36 | else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) 37 | { 38 | startingPath = Environment.CurrentDirectory; 39 | if (string.IsNullOrEmpty(startingPath)) 40 | { 41 | startingPath = AppContext.BaseDirectory; 42 | } 43 | } 44 | 45 | RootFolder = startingPath; 46 | SetInternal(startingPath, refresh: false); 47 | OnlyAllowFolders = true; 48 | } 49 | 50 | public OpenFolderDialog(string startingPath, string? searchFilter = null) 51 | { 52 | if (File.Exists(startingPath)) 53 | { 54 | startingPath = Path.GetDirectoryName(startingPath) ?? string.Empty; 55 | } 56 | else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) 57 | { 58 | startingPath = Environment.CurrentDirectory; 59 | if (string.IsNullOrEmpty(startingPath)) 60 | { 61 | startingPath = AppContext.BaseDirectory; 62 | } 63 | } 64 | 65 | RootFolder = startingPath; 66 | SetInternal(startingPath, refresh: false); 67 | OnlyAllowFolders = true; 68 | 69 | if (searchFilter != null) 70 | { 71 | AllowedExtensions.AddRange(searchFilter.Split(['|'], StringSplitOptions.RemoveEmptyEntries)); 72 | } 73 | } 74 | 75 | public override string Name { get; } = "Folder Picker"; 76 | 77 | protected override ImGuiWindowFlags Flags { get; } = ImGuiWindowFlags.NoDocking; 78 | 79 | public string? SelectedFolder 80 | { 81 | get => selection.Count > 0 ? selection[0] : null; 82 | } 83 | 84 | public bool AllowMultipleSelection 85 | { 86 | get => selection.AllowMultipleSelection; 87 | set => selection.AllowMultipleSelection = value; 88 | } 89 | 90 | public IReadOnlyList Selection => selection; 91 | 92 | protected override void DrawContent() 93 | { 94 | DrawExplorer(); 95 | 96 | var selectionString = selection.SelectionString; 97 | if (ImGui.InputText("Selected"u8, ref selectionString, 1024, ImGuiInputTextFlags.EnterReturnsTrue)) 98 | { 99 | selection.SetSelectionString(selectionString, GetValidationOptions()); 100 | if (ImGui.IsKeyPressed(ImGuiKey.Enter)) 101 | { 102 | selection.Validate(GetValidationOptions()); 103 | } 104 | } 105 | 106 | ImGui.SameLine(); 107 | if (ImGui.Button("Cancel"u8)) 108 | { 109 | Close(DialogResult.Cancel); 110 | } 111 | 112 | ImGui.SameLine(); 113 | if (ImGui.Button("Open"u8)) 114 | { 115 | selection.Validate(GetValidationOptions()); 116 | Close(selection.Count > 0 ? DialogResult.Ok : DialogResult.Failed); 117 | } 118 | } 119 | 120 | private static MultiSelection.ValidationOptions GetValidationOptions() 121 | { 122 | return MultiSelection.ValidationOptions.MustExist | MultiSelection.ValidationOptions.AllowFolders; 123 | } 124 | 125 | public override void Reset() 126 | { 127 | selection.Clear(); 128 | base.Reset(); 129 | } 130 | 131 | protected override void OnCurrentFolderChanged(string old, string value) 132 | { 133 | selection.RootPath = value; 134 | if (!AllowMultipleSelection) 135 | { 136 | selection.Clear(); 137 | selection.Add(value); 138 | } 139 | } 140 | 141 | protected override bool IsSelected(FileSystemItem entry) 142 | { 143 | if (entry.IsFile) 144 | { 145 | return false; 146 | } 147 | 148 | return selection.Contains(entry.Path); 149 | } 150 | 151 | protected override void OnClicked(FileSystemItem entry, bool shift, bool ctrl) 152 | { 153 | if (entry.IsFile) 154 | { 155 | return; 156 | } 157 | 158 | if (shift) 159 | { 160 | string? last = selection.Count > 0 ? selection[^1] : null; 161 | if (last == null) 162 | { 163 | return; 164 | } 165 | 166 | // Avoid querying the file system by setting the field directly and not calling the constructor. 167 | FileSystemItem lastEntry = new() { Path = last }; 168 | 169 | if (FindRange(entry, lastEntry, out int startIndex, out int endIndex)) 170 | { 171 | for (int i = startIndex; i <= endIndex; i++) 172 | { 173 | selection.Add(Entries[i].Path); 174 | } 175 | } 176 | } 177 | else if (ctrl) 178 | { 179 | selection.Add(entry.Path); 180 | } 181 | else 182 | { 183 | selection.Clear(); 184 | selection.Add(entry.Path); 185 | } 186 | } 187 | 188 | protected override void OnEnterPressed() 189 | { 190 | selection.Validate(GetValidationOptions()); 191 | if (selection.Count == 0) 192 | { 193 | return; 194 | } 195 | 196 | Close(DialogResult.Ok); 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/PathValidator.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.Utilities.IO; 4 | using System.Runtime.InteropServices; 5 | 6 | public static class PathValidator 7 | { 8 | private static readonly char[] invalidPathChars = Path.GetInvalidPathChars(); 9 | private static readonly char[] dirSeparators = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]; 10 | 11 | public static bool IsValidPath(this string path) 12 | { 13 | if (string.IsNullOrWhiteSpace(path)) 14 | { 15 | return false; 16 | } 17 | 18 | return IsValidPath(path.AsSpan()); 19 | } 20 | 21 | public static bool IsValidPath(this ReadOnlySpan path) 22 | { 23 | while (path.Length > 0) 24 | { 25 | int index = path.LastIndexOfAny(dirSeparators); 26 | if (index <= 0) 27 | { 28 | break; // Just exit here and do the last check outside of the loop this prevents double checking the root part. 29 | } 30 | 31 | // exclude the separator 32 | var part = path[(index + 1)..]; 33 | 34 | // Check the part of the path 35 | if (part.IndexOfAny(invalidPathChars) != -1) 36 | { 37 | return false; 38 | } 39 | 40 | // exclude the part and separator 41 | path = path[..index]; 42 | } 43 | 44 | // Check the last remaining segment of the path 45 | if (FileUtils.IsPathRooted(path)) 46 | { 47 | return CheckRoot(path); 48 | } 49 | 50 | // If its not rooted, check the last segment 51 | if (path.IndexOfAny(invalidPathChars) != -1) 52 | { 53 | return false; 54 | } 55 | 56 | return true; 57 | } 58 | 59 | private static bool CheckRoot(ReadOnlySpan path) 60 | { 61 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 62 | { 63 | // Check if the drive letter is valid for Windows paths 64 | ReadOnlySpan root = FileUtils.GetPathRoot(path); 65 | if (root.Length > 1 && root[1] == ':') 66 | { 67 | char driveLetter = root[0]; 68 | if (!(driveLetter >= 'A' && driveLetter <= 'Z' || driveLetter >= 'a' && driveLetter <= 'z')) 69 | { 70 | return false; 71 | } 72 | } 73 | } 74 | else 75 | { 76 | // On Unix-based systems, root should be just '/' 77 | if (path.Length == 0 || path[0] != Path.DirectorySeparatorChar) 78 | { 79 | return false; 80 | } 81 | } 82 | return true; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/PopupManager.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using System.Collections.Generic; 4 | 5 | public static class PopupManager 6 | { 7 | private static readonly List popups = new(); 8 | private static readonly object _lock = new(); 9 | 10 | public static object SyncLock => _lock; 11 | 12 | public static IReadOnlyList Popups => popups; 13 | 14 | public static void Add(IPopup popup, bool show) 15 | { 16 | lock (_lock) 17 | { 18 | if (!popups.Contains(popup)) 19 | { 20 | popups.Add(popup); 21 | } 22 | 23 | if (show) 24 | { 25 | popup.Show(true); 26 | } 27 | } 28 | } 29 | 30 | public static void Remove(IPopup popup, bool close) 31 | { 32 | lock (_lock) 33 | { 34 | int idx = popups.IndexOf(popup); 35 | if (idx != -1) 36 | { 37 | return; 38 | } 39 | 40 | if (close) 41 | { 42 | popup.Close(true); 43 | } 44 | popups.RemoveAt(idx); 45 | } 46 | } 47 | 48 | public static void Draw() 49 | { 50 | lock (_lock) 51 | { 52 | if (popups.Count == 0) 53 | { 54 | return; 55 | } 56 | 57 | var popup = popups[^1]; 58 | popup.Draw(); 59 | if (!popup.Shown) 60 | { 61 | popups.RemoveAt(popups.Count - 1); 62 | if (popups.Count == 0) 63 | { 64 | return; 65 | } 66 | 67 | popups[^1].Show(); 68 | } 69 | } 70 | } 71 | 72 | public static void Clear() 73 | { 74 | lock (_lock) 75 | { 76 | for (int i = 0; i < popups.Count; i++) 77 | { 78 | popups[i].Close(); 79 | } 80 | popups.Clear(); 81 | } 82 | } 83 | 84 | public static void Dispose() 85 | { 86 | Clear(); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/RenameFileDialog.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Numerics; 5 | 6 | [Flags] 7 | public enum RenameDialogFlags 8 | { 9 | None, 10 | SourceMustExist = 1, 11 | OverwriteDestination = 2, 12 | NoAutomaticMove = 4, 13 | Directory = 8, 14 | Default = SourceMustExist | OverwriteDestination 15 | } 16 | 17 | public class RenameDialog : Dialog 18 | { 19 | private string sourcePath = string.Empty; 20 | private string filename = string.Empty; 21 | private string currentDirectory = string.Empty; 22 | private string destinationPath = string.Empty; 23 | 24 | private RenameDialogFlags flags; 25 | 26 | private string? warnMessage; 27 | private bool isError; 28 | private bool firstFrame = true; 29 | private int maxPathLength = 255; 30 | private Exception? exception; 31 | 32 | public RenameDialog(RenameDialogFlags flags = RenameDialogFlags.Default) : this(string.Empty, flags) 33 | { 34 | this.flags = flags; 35 | } 36 | 37 | public RenameDialog(string sourcePath, RenameDialogFlags flags = RenameDialogFlags.Default) 38 | { 39 | this.flags = flags; 40 | SourcePath = sourcePath; 41 | } 42 | 43 | public string SourcePath 44 | { 45 | get => sourcePath; 46 | set 47 | { 48 | value = Path.GetFullPath(value); 49 | bool fileExists = File.Exists(value); 50 | bool directoryExists = Directory.Exists(value); 51 | if (!fileExists && !directoryExists) 52 | { 53 | return; 54 | } 55 | 56 | IsDirectory = directoryExists; 57 | sourcePath = value; 58 | filename = Path.GetFileName(sourcePath); 59 | currentDirectory = Path.GetDirectoryName(sourcePath)!; 60 | destinationPath = sourcePath; 61 | } 62 | } 63 | 64 | public string? DestinationPath => destinationPath; 65 | 66 | public string CurrentDirectory => currentDirectory; 67 | 68 | public bool Overwrite { get => (flags & RenameDialogFlags.OverwriteDestination) != 0; set => SetFlag(RenameDialogFlags.OverwriteDestination, value); } 69 | 70 | public bool NoAutomaticMove { get => (flags & RenameDialogFlags.NoAutomaticMove) != 0; set => SetFlag(RenameDialogFlags.NoAutomaticMove, value); } 71 | 72 | public bool SourceMustExist { get => (flags & RenameDialogFlags.SourceMustExist) != 0; set => SetFlag(RenameDialogFlags.SourceMustExist, value); } 73 | 74 | public bool IsDirectory { get => (flags & RenameDialogFlags.Directory) != 0; private set => SetFlag(RenameDialogFlags.Directory, value); } 75 | 76 | public int MaxPathLength { get => maxPathLength; set => maxPathLength = value; } 77 | 78 | public Exception? Exception => exception; 79 | 80 | public override string Name { get; } = "Rename"; 81 | 82 | protected override ImGuiWindowFlags Flags { get; } = ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.AlwaysAutoResize; 83 | 84 | public override void Reset() 85 | { 86 | base.Reset(); 87 | firstFrame = true; 88 | exception = null; 89 | warnMessage = null; 90 | isError = false; 91 | } 92 | 93 | private void SetFlag(RenameDialogFlags flag, bool value) 94 | { 95 | if (value) 96 | { 97 | flags |= flag; 98 | } 99 | else 100 | { 101 | flags &= ~flag; 102 | } 103 | } 104 | 105 | protected override void DrawContent() 106 | { 107 | if (ImGui.InputText("New name", ref filename, (ulong)maxPathLength)) 108 | { 109 | destinationPath = Path.Combine(currentDirectory, filename); 110 | warnMessage = null; 111 | isError = false; 112 | if (destinationPath != sourcePath) 113 | { 114 | if (IsDirectory && Directory.Exists(destinationPath)) 115 | { 116 | warnMessage = "A directory with the name already exists!"; 117 | } 118 | else if (File.Exists(destinationPath)) 119 | { 120 | warnMessage = "A file with the name already exists!"; 121 | } 122 | } 123 | 124 | if (string.IsNullOrWhiteSpace(filename)) 125 | { 126 | warnMessage = "Name cannot be empty or whitespace"; 127 | isError = true; 128 | } 129 | } 130 | 131 | if (firstFrame) 132 | { 133 | ImGui.SetKeyboardFocusHere(-1); 134 | firstFrame = false; 135 | } 136 | 137 | if (warnMessage != null) ImGui.TextColored(isError ? new Vector4(1, 0, 0, 1) : new Vector4(1, 1, 0, 1), warnMessage); 138 | 139 | if (ImGui.Button("Cancel")) 140 | { 141 | Close(DialogResult.Cancel); 142 | } 143 | ImGui.SameLine(); 144 | ImGui.BeginDisabled((!Overwrite && warnMessage != null) || isError); 145 | if (ImGui.Button("Ok")) 146 | { 147 | ImGui.EndDisabled(); 148 | if (NoAutomaticMove || destinationPath == sourcePath) 149 | { 150 | Close(DialogResult.Ok); 151 | return; 152 | } 153 | 154 | try 155 | { 156 | if (IsDirectory) 157 | { 158 | if (Directory.Exists(destinationPath)) 159 | { 160 | if (!Overwrite) 161 | { 162 | Close(DialogResult.Failed); 163 | return; 164 | } 165 | Directory.Delete(destinationPath, true); 166 | } 167 | 168 | Directory.Move(sourcePath, destinationPath); 169 | } 170 | else 171 | { 172 | if (File.Exists(destinationPath)) 173 | { 174 | if (!Overwrite) 175 | { 176 | Close(DialogResult.Failed); 177 | return; 178 | } 179 | File.Delete(destinationPath); 180 | } 181 | 182 | File.Move(sourcePath, destinationPath); 183 | } 184 | } 185 | catch (Exception ex) 186 | { 187 | exception = ex; 188 | } 189 | 190 | Close(DialogResult.Ok); 191 | } 192 | else 193 | { 194 | ImGui.EndDisabled(); 195 | } 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/SaveFileDialog.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Collections.Generic; 5 | 6 | public class SaveFileDialog : FileDialogBase 7 | { 8 | private string selectedFile = string.Empty; 9 | 10 | public SaveFileDialog() 11 | { 12 | string startingPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 13 | if (File.Exists(startingPath)) 14 | { 15 | startingPath = Path.GetDirectoryName(startingPath) ?? string.Empty; 16 | } 17 | else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) 18 | { 19 | startingPath = Environment.CurrentDirectory; 20 | if (string.IsNullOrEmpty(startingPath)) 21 | startingPath = AppContext.BaseDirectory; 22 | } 23 | 24 | RootFolder = startingPath; 25 | SetInternal(startingPath, false); 26 | OnlyAllowFolders = false; 27 | } 28 | 29 | public SaveFileDialog(string startingPath) 30 | { 31 | if (File.Exists(startingPath)) 32 | { 33 | startingPath = Path.GetDirectoryName(startingPath) ?? string.Empty; 34 | } 35 | else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) 36 | { 37 | startingPath = Environment.CurrentDirectory; 38 | if (string.IsNullOrEmpty(startingPath)) 39 | startingPath = AppContext.BaseDirectory; 40 | } 41 | 42 | RootFolder = startingPath; 43 | SetInternal(startingPath, false); 44 | OnlyAllowFolders = false; 45 | } 46 | 47 | public SaveFileDialog(string startingPath, string? searchFilter = null) 48 | { 49 | if (File.Exists(startingPath)) 50 | { 51 | startingPath = Path.GetDirectoryName(startingPath) ?? string.Empty; 52 | } 53 | else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) 54 | { 55 | startingPath = Environment.CurrentDirectory; 56 | if (string.IsNullOrEmpty(startingPath)) 57 | startingPath = AppContext.BaseDirectory; 58 | } 59 | 60 | RootFolder = startingPath; 61 | SetInternal(startingPath, false); 62 | OnlyAllowFolders = false; 63 | 64 | if (searchFilter != null) 65 | { 66 | AllowedExtensions.AddRange(searchFilter.Split(['|'], StringSplitOptions.RemoveEmptyEntries)); 67 | } 68 | } 69 | 70 | public override string Name => "Save File"; 71 | 72 | public string SelectedFile 73 | { 74 | get => selectedFile; 75 | set 76 | { 77 | if (!File.Exists(value)) 78 | { 79 | return; 80 | } 81 | 82 | selectedFile = value; 83 | CurrentFolder = Path.GetDirectoryName(value) ?? string.Empty; 84 | } 85 | } 86 | 87 | protected override ImGuiWindowFlags Flags { get; } = ImGuiWindowFlags.NoDocking; 88 | 89 | protected override void DrawContent() 90 | { 91 | DrawExplorer(); 92 | 93 | ImGui.InputText("Selected", ref selectedFile, 1024); 94 | 95 | ImGui.SameLine(); 96 | if (ImGui.Button("Cancel")) 97 | { 98 | Close(DialogResult.Cancel); 99 | } 100 | 101 | if (OnlyAllowFolders) 102 | { 103 | ImGui.SameLine(); 104 | if (ImGui.Button("Save")) 105 | { 106 | SelectedFile = CurrentFolder; 107 | ValidateAndClose(); 108 | } 109 | } 110 | else if (selectedFile != null) 111 | { 112 | ImGui.SameLine(); 113 | if (ImGui.Button("Save")) 114 | { 115 | ValidateAndClose(); 116 | } 117 | } 118 | } 119 | 120 | protected override void OnCurrentFolderChanged(string old, string value) 121 | { 122 | selectedFile = string.Empty; 123 | } 124 | 125 | protected override bool IsSelected(FileSystemItem entry) 126 | { 127 | if (OnlyAllowFolders ^ entry.IsFile) 128 | { 129 | return false; 130 | } 131 | 132 | return entry.Path == selectedFile; 133 | } 134 | 135 | protected override void OnClicked(FileSystemItem entry, bool shift, bool ctrl) 136 | { 137 | selectedFile = entry.Path; 138 | } 139 | 140 | protected override void OnDoubleClicked(FileSystemItem entry, bool shift, bool ctrl) 141 | { 142 | base.OnDoubleClicked(entry, shift, ctrl); 143 | if (OnlyAllowFolders ^ entry.IsFile) 144 | { 145 | ValidateAndClose(); 146 | } 147 | } 148 | 149 | protected override void OnEnterPressed() 150 | { 151 | ValidateAndClose(); 152 | } 153 | 154 | private void ValidateAndClose() 155 | { 156 | if (string.IsNullOrWhiteSpace(selectedFile)) 157 | { 158 | return; 159 | } 160 | 161 | if (OnlyAllowFolders && Directory.Exists(selectedFile) || !OnlyAllowFolders && File.Exists(selectedFile)) 162 | { 163 | DialogMessageBox messageBox = new("File already exists", "Do you want to overwrite the file?", DialogMessageBoxType.YesNo); 164 | messageBox.Show(OverwriteMessageBoxCallback, this, DialogFlags.CenterOnParent); 165 | return; 166 | } 167 | 168 | Close(DialogResult.Ok); 169 | } 170 | 171 | private void OverwriteMessageBoxCallback(object? sender, DialogResult result) 172 | { 173 | if (result == DialogResult.Yes) 174 | { 175 | Close(DialogResult.Ok); 176 | } 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/SearchFilterDate.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public enum SearchFilterDate 4 | { 5 | None, 6 | Today, 7 | Yesterday, 8 | Week, 9 | Month, 10 | LastMonth, 11 | Year, 12 | LastYear 13 | } 14 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/SearchFilterSize.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public enum SearchFilterSize 4 | { 5 | None, 6 | Empty, 7 | Tiny, 8 | Small, 9 | Medium, 10 | Large, 11 | Huge, 12 | Gigantic 13 | } 14 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/SearchOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | using Hexa.NET.Utilities.IO; 4 | using System; 5 | using System.IO; 6 | 7 | public struct SearchOptions 8 | { 9 | public bool Enabled; 10 | public string Pattern = string.Empty; 11 | public SearchOptionsFlags Flags; 12 | public SearchFilterDate DateModified; 13 | public SearchFilterSize FileSize; 14 | 15 | public SearchOptions() 16 | { 17 | } 18 | 19 | public readonly bool Filter(in FileMetadata metadata) 20 | { 21 | if ((metadata.Attributes & FileAttributes.Directory) != 0) 22 | { 23 | return true; 24 | } 25 | 26 | if ((Flags & SearchOptionsFlags.FilterDate) != 0) 27 | { 28 | DateTime now = DateTime.Now; 29 | DateTime startDate = DateTime.MinValue; 30 | 31 | switch (DateModified) 32 | { 33 | case SearchFilterDate.Today: 34 | startDate = now.Date; // Start of today 35 | break; 36 | 37 | case SearchFilterDate.Yesterday: 38 | startDate = now.Date.AddDays(-1); 39 | break; 40 | 41 | case SearchFilterDate.Week: 42 | startDate = now.AddDays(-7); 43 | break; 44 | 45 | case SearchFilterDate.Month: 46 | startDate = now.AddMonths(-1); 47 | break; 48 | 49 | case SearchFilterDate.LastMonth: 50 | startDate = new DateTime(now.Year, now.Month, 1).AddMonths(-1); 51 | break; 52 | 53 | case SearchFilterDate.Year: 54 | startDate = now.AddYears(-1); 55 | break; 56 | 57 | case SearchFilterDate.LastYear: 58 | startDate = new DateTime(now.Year - 1, 1, 1); 59 | break; 60 | } 61 | 62 | // If the item doesn't match the date filter, return false 63 | if (metadata.LastWriteTime < startDate) 64 | { 65 | return false; 66 | } 67 | } 68 | 69 | if ((Flags & SearchOptionsFlags.FilterSize) != 0) 70 | { 71 | long minSize = 0; 72 | long maxSize = long.MaxValue; 73 | 74 | switch (FileSize) 75 | { 76 | case SearchFilterSize.Empty: 77 | maxSize = 0; 78 | break; 79 | 80 | case SearchFilterSize.Tiny: 81 | minSize = 1; 82 | maxSize = 1024; // Up to 1 KB 83 | break; 84 | 85 | case SearchFilterSize.Small: 86 | minSize = 1024; 87 | maxSize = 1024 * 1024; // 1 KB to 1 MB 88 | break; 89 | 90 | case SearchFilterSize.Medium: 91 | minSize = 1024 * 1024; 92 | maxSize = 1024 * 1024 * 10; // 1 MB to 10 MB 93 | break; 94 | 95 | case SearchFilterSize.Large: 96 | minSize = 1024 * 1024 * 10; 97 | maxSize = 1024 * 1024 * 100; // 10 MB to 100 MB 98 | break; 99 | 100 | case SearchFilterSize.Huge: 101 | minSize = 1024 * 1024 * 100; 102 | maxSize = 1024 * 1024 * 1000; // 100 MB to 1 GB 103 | break; 104 | 105 | case SearchFilterSize.Gigantic: 106 | minSize = 1024 * 1024 * 1000; // Greater than 1 GB 107 | break; 108 | } 109 | 110 | // If the item doesn't match the size filter, return false 111 | if (metadata.Size < minSize || metadata.Size > maxSize) 112 | { 113 | return false; 114 | } 115 | } 116 | 117 | return true; 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Dialogs/SearchOptionsFlags.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Dialogs 2 | { 3 | public enum SearchOptionsFlags 4 | { 5 | None = 0, 6 | Subfolders = 1, 7 | Hidden = 2, 8 | SystemFiles = 4, 9 | FilterDate = 8, 10 | FilterSize = 16, 11 | } 12 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Extensions/EnumExtension.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extensions 2 | { 3 | using System.Runtime.CompilerServices; 4 | 5 | /// 6 | /// Provides extension methods for enumerations to cast them to integral types. 7 | /// 8 | public static class EnumExtension 9 | { 10 | /// 11 | /// Casts an enumeration value to the specified integral type, with type size checking. 12 | /// 13 | /// The enumeration type. 14 | /// The integral type to cast to. 15 | /// The enumeration value to cast. 16 | /// The casted value of the specified integral type. 17 | /// Thrown when the size of the enumeration does not match the size of the target integral type. 18 | public static TInt AsInteger(this TEnum enumValue) 19 | where TEnum : unmanaged, Enum 20 | where TInt : unmanaged 21 | { 22 | if (Unsafe.SizeOf() != Unsafe.SizeOf()) throw new Exception("type mismatch"); 23 | TInt value = Unsafe.As(ref enumValue); 24 | return value; 25 | } 26 | 27 | /// 28 | /// Casts an enumeration value to a long integral type with size checking. 29 | /// 30 | /// The enumeration type. 31 | /// The enumeration value to cast. 32 | /// The casted value of type long. 33 | /// Thrown when the size of the enumeration does not match supported integral types. 34 | public static long AsInteger(this TEnum enumValue) 35 | where TEnum : unmanaged, Enum 36 | { 37 | long value; 38 | if (Unsafe.SizeOf() != Unsafe.SizeOf()) value = Unsafe.As(ref enumValue); 39 | else if (Unsafe.SizeOf() != Unsafe.SizeOf()) value = Unsafe.As(ref enumValue); 40 | else if (Unsafe.SizeOf() != Unsafe.SizeOf()) value = Unsafe.As(ref enumValue); 41 | else if (Unsafe.SizeOf() != Unsafe.SizeOf()) value = Unsafe.As(ref enumValue); 42 | else throw new Exception("type mismatch"); 43 | return value; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Extensions/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extensions 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | public static class ListExtensions 7 | { 8 | public static bool Contains(this List list, ReadOnlySpan value, StringComparison comparisonType) 9 | { 10 | foreach (var item in list) 11 | { 12 | if (item.AsSpan().Equals(value, comparisonType)) 13 | { 14 | return true; 15 | } 16 | } 17 | 18 | return false; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Extensions/SpanHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Extensions 2 | { 3 | using System; 4 | using System.Text; 5 | 6 | public static unsafe class SpanHelper 7 | { 8 | public static ReadOnlySpan TrimText(this ReadOnlySpan text, char c) 9 | { 10 | int start = 0; 11 | while (start < text.Length && text[start] == c) start++; 12 | 13 | if (start == text.Length) return ReadOnlySpan.Empty; 14 | 15 | int end = 0; 16 | while (end < text.Length && text[text.Length - 1 - end] == c) end++; 17 | 18 | return text.Slice(start, text.Length - start - end); 19 | } 20 | 21 | public static string TrimText(this string text, char c) 22 | { 23 | var span = TrimText(text.AsSpan(), c); 24 | return span.Length == text.Length ? text : span.ToString(); 25 | } 26 | 27 | public static ReadOnlySpan GetRelativePath(this ReadOnlySpan path, ReadOnlySpan relativeTo) 28 | { 29 | if (path.StartsWith(relativeTo)) 30 | { 31 | path = path[relativeTo.Length..]; 32 | int i = 0; 33 | // trim folder seperators out. 34 | while (i < path.Length && path[i] == '/' || path[i] == '\\') i++; 35 | path = path[i..]; 36 | } 37 | 38 | return path; 39 | } 40 | 41 | public static string GetRelativePath(this string path, string relativeTo) 42 | { 43 | var relative = GetRelativePath(path.AsSpan(), relativeTo.AsSpan()); 44 | return relative.Length == path.Length ? path : relative.ToString(); 45 | } 46 | 47 | public static ReadOnlySpan CreateReadOnlySpanFromNullTerminated(char* pointer) 48 | { 49 | int len = StrLen(pointer); 50 | return new ReadOnlySpan(pointer, len); 51 | } 52 | 53 | public static ReadOnlySpan CreateReadOnlySpanFromNullTerminated(byte* pointer) 54 | { 55 | int len = StrLen(pointer); 56 | return new ReadOnlySpan(pointer, len); 57 | } 58 | 59 | public static bool StartsWith(this ReadOnlySpan span, char c) 60 | { 61 | return span.Length > 0 && span[0] == c; 62 | } 63 | 64 | #if NETSTANDARD2_0 65 | public static bool StartsWith(this string span, char c) 66 | { 67 | return span.Length > 0 && span[0] == c; 68 | } 69 | 70 | public static void Append(this StringBuilder sb, ReadOnlySpan span) 71 | { 72 | if (span.IsEmpty) 73 | return; 74 | 75 | // Ensure the StringBuilder has enough capacity to avoid resizing during append 76 | sb.EnsureCapacity(sb.Length + span.Length); 77 | 78 | // Manually append each character from the span to the StringBuilder 79 | foreach (char c in span) 80 | { 81 | sb.Append(c); 82 | } 83 | } 84 | 85 | public static int GetBytes(this Encoding encoding, ReadOnlySpan chars, Span bytes) 86 | { 87 | fixed (char* pChars = chars) 88 | { 89 | fixed (byte* pBytes = bytes) 90 | { 91 | return encoding.GetBytes(pChars, chars.Length, pBytes, bytes.Length); 92 | } 93 | } 94 | } 95 | 96 | public static int GetChars(this Encoding encoding, ReadOnlySpan bytes, Span chars) 97 | { 98 | fixed (byte* pBytes = bytes) 99 | { 100 | fixed (char* pChars = chars) 101 | { 102 | return encoding.GetChars(pBytes, bytes.Length, pChars, chars.Length); 103 | } 104 | } 105 | } 106 | 107 | public static int GetCharCount(this Encoding encoding, ReadOnlySpan bytes) 108 | { 109 | fixed (byte* pBytes = bytes) 110 | { 111 | return encoding.GetCharCount(pBytes, bytes.Length); 112 | } 113 | } 114 | 115 | public static int GetByteCount(this Encoding encoding, ReadOnlySpan chars) 116 | { 117 | fixed (char* pChars = chars) 118 | { 119 | return encoding.GetByteCount(pChars, chars.Length); 120 | } 121 | } 122 | 123 | #endif 124 | } 125 | 126 | public static unsafe class CollectionsExtensions 127 | { 128 | public static void AddRange(this IList list, Span values) 129 | { 130 | for (int i = 0; i < values.Length; i++) 131 | { 132 | list.Add(values[i]); 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Globals.cs: -------------------------------------------------------------------------------- 1 | global using static Hexa.NET.Utilities.Utils; -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Hexa.NET.ImGui.Widgets.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0;net9.0-android;net8.0;net8.0-android;netstandard2.1;netstandard2.0 4 | enable 5 | enable 6 | true 7 | 8 | 12 9 | 10 | true 11 | true 12 | true 13 | true 14 | 15 | 1.2.12 16 | 1.2.12 17 | 18 | Hexa.NET.ImGui.Widgets is a comprehensive library of custom widgets for the ImGui graphical user interface library. This package includes a variety of pre-built widgets that enhance the functionality and usability of ImGui in your .NET applications. Each widget is designed to be easy to integrate, with consistent styling and behavior. This library is an extension of the Hexa.NET.ImGui wrapper, providing additional UI components for a seamless user experience. 19 | 20 | 21 | Hexa.NET, ImGui, GUI, Widgets, UI, User Interface, .NET, C#, Custom Widgets, HexaNET, ImGuiWrapper 22 | 23 | Juna Meinhold 24 | Copyright (c) 2024 Juna Meinhold 25 | https://github.com/HexaEngine/Hexa.NET.ImGui 26 | https://github.com/HexaEngine/Hexa.NET.ImGui 27 | git 28 | LICENSE.txt 29 | README.md 30 | 31 | true 32 | $(NoWarn);1591 33 | 34 | true 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 | -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/IImGuiWindow.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | public interface IImGuiWindow : IUIElement 4 | { 5 | string Name { get; } 6 | 7 | bool IsEmbedded { get; internal set; } 8 | 9 | void Close(); 10 | 11 | void Dispose(); 12 | 13 | void DrawContent(); 14 | 15 | void DrawWindow(ImGuiWindowFlags overwriteFlags); 16 | 17 | void Init(); 18 | 19 | void Show(); 20 | } 21 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/IUIElement.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using System.Numerics; 4 | 5 | public interface IUIElement 6 | { 7 | public Vector2 Position { get; } 8 | 9 | public Vector2 Size { get; } 10 | 11 | public uint ViewportId { get; } 12 | } 13 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImGuiAnimationHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.Utilities; 4 | 5 | public struct AnimationState 6 | { 7 | public float Time; 8 | public float Speed; 9 | public float Duration; 10 | public AnimationType Type; 11 | } 12 | 13 | public enum AnimationType 14 | { 15 | Linear, 16 | EaseIn, 17 | EaseOut, 18 | EaseInOut, 19 | Bounce, 20 | EaseInQuad, 21 | EaseOutQuad, 22 | EaseInOutQuad, 23 | EaseInCubic, 24 | EaseOutCubic, 25 | EaseInOutCubic 26 | } 27 | 28 | public static class AnimationManager 29 | { 30 | private static UnsafeDictionary states = new(); 31 | private static UnsafeQueue removeQueue = new(); 32 | 33 | public static bool AnyActive => states.Count != 0; 34 | 35 | public static int ActiveCount => states.Count; 36 | 37 | public static void Clear() 38 | { 39 | states.Clear(); 40 | removeQueue.Clear(); 41 | } 42 | 43 | public static void Release() 44 | { 45 | states.Release(); 46 | removeQueue.Release(); 47 | } 48 | 49 | public static void ResetAnimation(uint id) 50 | { 51 | if (states.TryGetValue(id, out AnimationState value)) 52 | { 53 | value.Time = 0; 54 | states[id] = value; 55 | } 56 | } 57 | 58 | public static void StopAnimation(uint id) 59 | { 60 | states.Remove(id); 61 | } 62 | 63 | public static void AddAnimation(uint id, float duration, float speed, AnimationType type) 64 | { 65 | states[id] = new AnimationState 66 | { 67 | Time = 0, 68 | Speed = speed, 69 | Duration = duration, 70 | Type = type 71 | }; 72 | } 73 | 74 | public static void AddAnimation(uint id, float time, float duration, float speed, AnimationType type) 75 | { 76 | states[id] = new AnimationState 77 | { 78 | Time = time, 79 | Speed = speed, 80 | Duration = duration, 81 | Type = type 82 | }; 83 | } 84 | 85 | public static void RemoveAnimation(uint id) 86 | { 87 | if (states.ContainsKey(id)) 88 | { 89 | states.Remove(id); 90 | } 91 | } 92 | 93 | public static void Tick() 94 | { 95 | var deltaTime = ImGui.GetIO().DeltaTime; 96 | foreach (var state in states) 97 | { 98 | var value = state.Value; 99 | value.Time += deltaTime * value.Speed; 100 | states[state.Key] = value; 101 | if (value.Time >= value.Duration) 102 | { 103 | removeQueue.Enqueue(state.Key); 104 | } 105 | } 106 | 107 | while (removeQueue.TryDequeue(out var key)) 108 | { 109 | states.Remove(key); 110 | } 111 | } 112 | 113 | public static float GetAnimationValue(uint id) 114 | { 115 | if (!states.TryGetValue(id, out AnimationState value)) return -1; 116 | 117 | var state = value; 118 | float t = state.Time / state.Duration; 119 | 120 | switch (state.Type) 121 | { 122 | case AnimationType.Linear: 123 | return t; 124 | 125 | case AnimationType.EaseIn: 126 | return t * t; 127 | 128 | case AnimationType.EaseOut: 129 | return t * (2 - t); 130 | 131 | case AnimationType.EaseInOut: 132 | return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; 133 | 134 | case AnimationType.Bounce: 135 | return BounceEaseOut(t); 136 | 137 | case AnimationType.EaseInQuad: 138 | return t * t; 139 | 140 | case AnimationType.EaseOutQuad: 141 | return t * (2 - t); 142 | 143 | case AnimationType.EaseInOutQuad: 144 | return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; 145 | 146 | case AnimationType.EaseInCubic: 147 | return t * t * t; 148 | 149 | case AnimationType.EaseOutCubic: 150 | t--; 151 | return t * t * t + 1; 152 | 153 | case AnimationType.EaseInOutCubic: 154 | return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; 155 | 156 | default: 157 | return t; 158 | } 159 | } 160 | 161 | private static float BounceEaseOut(float t) 162 | { 163 | if (t < 1 / 2.75f) 164 | { 165 | return 7.5625f * t * t; 166 | } 167 | else if (t < 2 / 2.75f) 168 | { 169 | t -= 1.5f / 2.75f; 170 | return 7.5625f * t * t + 0.75f; 171 | } 172 | else if (t < 2.5f / 2.75f) 173 | { 174 | t -= 2.25f / 2.75f; 175 | return 7.5625f * t * t + 0.9375f; 176 | } 177 | else 178 | { 179 | t -= 2.625f / 2.75f; 180 | return 7.5625f * t * t + 0.984375f; 181 | } 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImGuiFileTreeView.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui.Widgets.Dialogs; 4 | using System.Numerics; 5 | 6 | public class ImGuiFileTreeView 7 | { 8 | public static bool FileTreeView(string strId, Vector2 size, ref string currentFolder, string homeFolder) 9 | { 10 | if (!ImGui.BeginChild(strId, size)) 11 | { 12 | ImGui.EndChild(); 13 | return false; 14 | } 15 | 16 | static bool Display(FileSystemItem item, ref string currentFolder, bool first = true) 17 | { 18 | if ((item.Flags & FileSystemItemFlags.Folder) == 0) 19 | { 20 | return false; 21 | } 22 | 23 | Vector4 color = item.IsFolder && !first ? new(1.0f, 0.87f, 0.37f, 1.0f) : new(1.0f, 1.0f, 1.0f, 1.0f); 24 | bool isOpen = ImGui.TreeNodeEx(item.Name, ImGuiTreeNodeFlags.OpenOnArrow); 25 | bool changed = false; 26 | if (ImGui.IsItemHovered() && ImGui.IsItemClicked(ImGuiMouseButton.Left)) 27 | { 28 | currentFolder = item.Path; 29 | changed = true; 30 | } 31 | 32 | if (isOpen) 33 | { 34 | foreach (var subFolder in FileSystemHelper.GetFileSystemEntries(item.Path, RefreshFlags.Folders | RefreshFlags.Hidden, null)) 35 | { 36 | changed |= Display(subFolder, ref currentFolder, false); 37 | } 38 | 39 | ImGui.TreePop(); 40 | } 41 | 42 | return changed; 43 | } 44 | 45 | bool changed = false; 46 | 47 | ImGui.Indent(); 48 | 49 | ImGui.Text($"{MaterialIcons.Home}"); 50 | ImGui.SameLine(); 51 | if (ImGui.Selectable("Home"u8, false, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.NoAutoClosePopups)) 52 | { 53 | currentFolder = homeFolder; 54 | changed = true; 55 | } 56 | ImGui.Unindent(); 57 | 58 | ImGui.Separator(); 59 | 60 | ImGui.Indent(); 61 | foreach (var dir in FileSystemHelper.SpecialDirs) 62 | { 63 | ImGui.Text(dir.Icon); 64 | ImGui.SameLine(); 65 | if (ImGui.Selectable(dir.Name, false, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.NoAutoClosePopups)) 66 | { 67 | currentFolder = dir.Path; 68 | changed = true; 69 | } 70 | } 71 | ImGui.Unindent(); 72 | ImGui.Separator(); 73 | 74 | ImGui.PushStyleVar(ImGuiStyleVar.IndentSpacing, 5f); 75 | if (ImGui.TreeNodeEx($"{MaterialIcons.Computer} Computer", ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.DefaultOpen)) 76 | { 77 | foreach (var dir in FileSystemHelper.LogicalDrives) 78 | { 79 | changed |= Display(dir, ref currentFolder); 80 | } 81 | ImGui.TreePop(); 82 | } 83 | ImGui.PopStyleVar(); 84 | 85 | ImGui.EndChild(); 86 | 87 | return changed; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImGuiGC.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.Utilities; 5 | using System.Diagnostics; 6 | using System.Runtime.InteropServices; 7 | 8 | public static unsafe class ImGuiGC 9 | { 10 | private static Dictionary trackingList = new(); 11 | private static HashSet gcList = new(); 12 | private static Queue gcFreeQueue = new(); 13 | private static float gcInterval = 10.0f; 14 | private static long lastGCTick; 15 | private readonly static object gcLock = new(); 16 | private static long lastAllocatedBytes; 17 | private static long allocatedBytes; 18 | private static long frequency; 19 | 20 | private struct ImGuiGCData 21 | { 22 | public uint Id; 23 | public void* Data; 24 | public int Size; 25 | } 26 | 27 | public static void Init() 28 | { 29 | ImGuiContextHook hook = new(); 30 | hook.Callback = (void*)Marshal.GetFunctionPointerForDelegate(HookCallback); 31 | hook.Type = ImGuiContextHookType.EndFramePost; 32 | 33 | ImGuiP.AddContextHook(ImGui.GetCurrentContext(), &hook); 34 | } 35 | 36 | public static bool KeepAlive(uint id, out T* ptr) where T : unmanaged 37 | { 38 | lock (gcLock) 39 | { 40 | if (trackingList.TryGetValue(id, out var gcData)) 41 | { 42 | gcList.Add(id); 43 | ptr = (T*)gcData.Data; 44 | 45 | return true; 46 | } 47 | } 48 | ptr = null; 49 | return false; 50 | } 51 | 52 | public static T* Alloc(uint id) where T : unmanaged 53 | { 54 | return Alloc(id, 1); 55 | } 56 | 57 | public static T* Alloc(uint id, int count) where T : unmanaged 58 | { 59 | int size = count * sizeof(T); 60 | void* data = Utils.Alloc(size); 61 | Alloc(id, data, size); 62 | return (T*)data; 63 | } 64 | 65 | public static void Alloc(uint id, void* data, int size) 66 | { 67 | lock (gcLock) 68 | { 69 | ImGuiGCData gcData = new(); 70 | gcData.Id = id; 71 | gcData.Data = data; 72 | gcData.Size = size; 73 | trackingList.Add(id, gcData); 74 | gcList.Add(id); 75 | } 76 | 77 | Interlocked.Add(ref allocatedBytes, size); 78 | Interlocked.Increment(ref frequency); 79 | } 80 | 81 | public static void Free(uint id) 82 | { 83 | lock (gcLock) 84 | { 85 | gcFreeQueue.Enqueue(id); // queue for deletion 86 | } 87 | } 88 | 89 | private static void FreeInternal(uint id) 90 | { 91 | var gcData = trackingList[id]; 92 | Utils.Free(gcData.Data); 93 | Interlocked.Add(ref allocatedBytes, -gcData.Size); 94 | trackingList.Remove(id); 95 | gcList.Remove(id); 96 | } 97 | 98 | private static void HookCallback 99 | #if NET5_0_OR_GREATER 100 | (ImGuiContext* ctx, ImGuiContextHook* hook) 101 | #else 102 | (nint ctx, nint hook) 103 | #endif 104 | { 105 | long memoryDelta = allocatedBytes - lastAllocatedBytes; 106 | if (frequency > 128 || memoryDelta > 4096) 107 | { 108 | Collect(); // force GC if too many objects are being created in a short time, or if memory usage is increasing too fast 109 | return; 110 | } 111 | 112 | // do a scheduled GC Collect. 113 | long now = Stopwatch.GetTimestamp(); 114 | long next = lastGCTick + (long)(gcInterval * Stopwatch.Frequency); 115 | if (now >= next) 116 | { 117 | Collect(); 118 | } 119 | } 120 | 121 | public static void Collect() 122 | { 123 | lock (gcLock) 124 | { 125 | lastGCTick = Stopwatch.GetTimestamp(); 126 | foreach (var item in trackingList) 127 | { 128 | if (!gcList.Contains(item.Key)) 129 | { 130 | gcFreeQueue.Enqueue(item.Key); 131 | } 132 | } 133 | #if NETSTANDARD2_0 134 | while (gcFreeQueue.Count > 0) 135 | { 136 | var id = gcFreeQueue.Dequeue(); 137 | FreeInternal(id); 138 | } 139 | #else 140 | while (gcFreeQueue.TryDequeue(out uint id)) 141 | { 142 | FreeInternal(id); 143 | } 144 | #endif 145 | gcList.Clear(); 146 | frequency = 0; 147 | lastAllocatedBytes = allocatedBytes; 148 | } 149 | } 150 | 151 | public static void Shutdown() 152 | { 153 | lock (gcLock) 154 | { 155 | foreach (var item in trackingList) 156 | { 157 | Utils.Free(item.Value.Data); 158 | } 159 | 160 | trackingList.Clear(); 161 | gcList.Clear(); 162 | gcFreeQueue.Clear(); 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImGuiName.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using System; 4 | 5 | /// 6 | /// A struct representing an identifier for ImGui widgets and elements. 7 | /// 8 | public struct ImGuiName 9 | { 10 | /// 11 | /// The raw identifier used to generate unique names. 12 | /// 13 | public string RawId; 14 | 15 | /// 16 | /// The primary name associated with the identifier. 17 | /// 18 | public string Name; 19 | 20 | /// 21 | /// The unique name generated by combining the primary name and a unique identifier. 22 | /// 23 | public string UniqueName; 24 | 25 | /// 26 | /// A simplified identifier used in certain contexts. 27 | /// 28 | public string Id; 29 | 30 | /// 31 | /// Initializes a new instance of the struct with a given name. 32 | /// 33 | /// The primary name to associate with the identifier. 34 | public ImGuiName(string name) 35 | { 36 | RawId = Guid.NewGuid().ToString(); 37 | Name = name; 38 | UniqueName = $"{Name}##{RawId}"; 39 | Id = $"##{RawId}"; 40 | } 41 | 42 | /// 43 | /// Implicitly converts an instance to a string. 44 | /// 45 | /// The instance to convert. 46 | /// The unique name as a string. 47 | public static implicit operator string(ImGuiName name) 48 | { 49 | return name.UniqueName; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImGuiProgressBar.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Drawing; 5 | using System.Numerics; 6 | 7 | public static class ImGuiProgressBar 8 | { 9 | public static unsafe void ProgressBar(float value, Vector2 size, uint backgroundColor, uint foregroundColor) 10 | { 11 | ImGuiWindow* window = ImGuiP.GetCurrentWindow(); 12 | if (window->SkipItems == 1) 13 | { 14 | return; 15 | } 16 | 17 | ImDrawList* drawList = ImGui.GetWindowDrawList(); 18 | ImGuiContextPtr g = ImGui.GetCurrentContext(); 19 | ImGuiStylePtr style = ImGui.GetStyle(); 20 | 21 | var avail = ImGui.GetContentRegionAvail(); 22 | 23 | if (size.X <= 0) 24 | { 25 | size.X += avail.X; 26 | } 27 | if (size.Y == 0) 28 | { 29 | size.Y = 10; 30 | } 31 | 32 | var pos = ImGui.GetCursorScreenPos(); 33 | 34 | pos += style.FramePadding; 35 | size += style.FramePadding * 2; 36 | 37 | ImRect bb = new(pos, pos + size); 38 | ImGuiP.ItemSize(bb, style.FramePadding.Y); 39 | 40 | value = Clamp(value, 0f, 1f); 41 | 42 | // Render 43 | Vector2 progressBBMax = new(pos.X + size.X * value, bb.Max.Y); 44 | 45 | drawList->AddRectFilled(bb.Min, bb.Max, backgroundColor); 46 | drawList->AddRectFilled(bb.Min, progressBBMax, foregroundColor); 47 | 48 | uint col_b = 0x6c000000; // ABGR 49 | 50 | float time = (float)g.Time; 51 | float speed = 5f * (100 / size.X); // 500px/s 52 | float gradient_pos = time * speed % 1; 53 | 54 | float gradient_width = 40; // Adjust gradient width here 55 | float gradient_start_x = bb.Min.X + gradient_pos * size.X; 56 | 57 | Vector2 gradient_p1 = new(gradient_start_x - gradient_width, bb.Min.Y); 58 | Vector2 gradient_p2 = new(gradient_start_x, bb.Max.Y); 59 | Vector2 gradient_p3 = new(gradient_start_x, bb.Min.Y); 60 | Vector2 gradient_p4 = new(gradient_start_x + gradient_width, bb.Max.Y); 61 | 62 | ImGui.PushClipRect(bb.Min, progressBBMax, true); 63 | 64 | drawList->AddRectFilledMultiColor(gradient_p1, gradient_p2, 0x0, col_b, col_b, 0x0); 65 | drawList->AddRectFilledMultiColor(gradient_p3, gradient_p4, col_b, 0x0, 0x0, col_b); 66 | 67 | ImGui.PopClipRect(); 68 | } 69 | 70 | public static float Clamp(float value, float min, float max) 71 | { 72 | if (value < min) return min; 73 | if (value > max) return max; 74 | return value; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImGuiSpinner.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using System; 5 | using System.Numerics; 6 | 7 | public static class ImGuiSpinner 8 | { 9 | public static unsafe void Spinner(float radius, float thickness, uint color) 10 | { 11 | ImGuiWindow* window = ImGuiP.GetCurrentWindow(); 12 | if (window->SkipItems == 1) 13 | { 14 | return; 15 | } 16 | 17 | ImDrawList* drawList = ImGui.GetWindowDrawList(); 18 | var g = ImGui.GetCurrentContext(); 19 | var style = ImGui.GetStyle(); 20 | 21 | var pos = ImGui.GetCursorScreenPos(); 22 | 23 | Vector2 size = new(radius * 2, (radius + style.FramePadding.Y) * 2); 24 | 25 | ImRect bb = new(pos, pos + size); 26 | 27 | ImGuiP.ItemSize(bb, -1); 28 | 29 | // Render 30 | ImGui.PathClear(drawList); 31 | 32 | const int num_segments = 24; 33 | 34 | int start = (int)Math.Abs(MathF.Sin((float)(g.Time * 1.8f)) * (num_segments - 5)); 35 | 36 | float a_min = (float)Math.PI * 2.0f * start / num_segments; 37 | float a_max = (float)Math.PI * 2.0f * ((float)num_segments - 3) / num_segments; 38 | 39 | Vector2 center = pos + new Vector2(radius, radius + style.FramePadding.Y); 40 | 41 | for (var i = 0; i < num_segments; i++) 42 | { 43 | float a = a_min + i / (float)num_segments * (a_max - a_min); 44 | var time = (float)g.Time; 45 | var pp = new Vector2(center.X + MathF.Cos(a + time * 8) * radius, center.Y + MathF.Sin(a + time * 8) * radius); 46 | drawList->PathLineTo(pp); 47 | } 48 | 49 | ImGui.PathStroke(drawList, color, 0, thickness); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImWindow.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Numerics; 5 | 6 | public delegate void SizeChangedEventHandler(object? sender, Vector2 oldSize, Vector2 size); 7 | 8 | public delegate void PositionChangedEventHandler(object? sender, Vector2 oldPosition, Vector2 position); 9 | 10 | public delegate void ViewportChangedEventHandler(object? sender, uint oldViewportId, uint viewportId); 11 | 12 | public abstract class ImWindow : IImGuiWindow 13 | { 14 | private bool isEmbedded; 15 | protected bool IsShown; 16 | protected bool IsDocked; 17 | protected bool windowEnded; 18 | private Vector2 size; 19 | private Vector2 position; 20 | private uint viewportId; 21 | 22 | public abstract string Name { get; } 23 | 24 | protected ImGuiWindowFlags Flags; 25 | 26 | public bool IsEmbedded { get => isEmbedded; protected set => isEmbedded = value; } 27 | 28 | bool IImGuiWindow.IsEmbedded { get => isEmbedded; set => isEmbedded = value; } 29 | 30 | public Vector2 Size 31 | { 32 | get => size; 33 | set 34 | { 35 | size = value; 36 | ImGui.SetWindowSize(Name, size); 37 | } 38 | } 39 | 40 | public Vector2 Position 41 | { 42 | get => position; 43 | set 44 | { 45 | position = value; 46 | ImGui.SetWindowPos(Name, position); 47 | } 48 | } 49 | 50 | public uint ViewportId => viewportId; 51 | 52 | public event SizeChangedEventHandler? SizeChanged; 53 | 54 | public event PositionChangedEventHandler? PositionChanged; 55 | 56 | public event ViewportChangedEventHandler? ViewportChanged; 57 | 58 | public virtual void Init() 59 | { 60 | } 61 | 62 | public virtual unsafe void DrawWindow(ImGuiWindowFlags overwriteFlags) 63 | { 64 | if (!IsShown) return; 65 | 66 | if (isEmbedded) 67 | { 68 | ImGuiWindowClass windowClass; 69 | windowClass.DockNodeFlagsOverrideSet = (ImGuiDockNodeFlags)ImGuiDockNodeFlagsPrivate.NoTabBar; 70 | ImGui.SetNextWindowClass(&windowClass); 71 | ImGui.SetNextWindowDockID(WidgetManager.DockSpaceId); 72 | } 73 | 74 | var windowFlags = Flags | overwriteFlags; 75 | 76 | if (!ImGui.Begin(Name, ref IsShown, windowFlags)) 77 | { 78 | size = Vector2.Zero; // window is closed. 79 | ImGui.End(); 80 | OnClosedInternal(); 81 | return; 82 | } 83 | 84 | if (!IsShown) 85 | { 86 | OnClosedInternal(); 87 | ImGui.End(); 88 | return; 89 | } 90 | 91 | if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) 92 | { 93 | var currentViewport = ImGui.GetWindowViewport().ID; 94 | 95 | if (viewportId != currentViewport) 96 | { 97 | var oldViewportId = viewportId; 98 | viewportId = currentViewport; 99 | OnViewportChangedInternal(oldViewportId, viewportId); 100 | } 101 | } 102 | 103 | var currentSize = ImGui.GetWindowSize(); 104 | 105 | if (size != currentSize) 106 | { 107 | var oldSize = size; 108 | size = currentSize; 109 | OnSizeChangedInternal(oldSize, size); 110 | } 111 | 112 | var currentPosition = ImGui.GetWindowPos(); 113 | 114 | if (position != currentPosition) 115 | { 116 | var oldPosition = position; 117 | position = currentPosition; 118 | OnPositionChangedInternal(oldPosition, position); 119 | } 120 | 121 | windowEnded = false; 122 | 123 | DrawContent(); 124 | 125 | if (!windowEnded) 126 | ImGui.End(); 127 | } 128 | 129 | private void OnPositionChangedInternal(Vector2 oldPosition, Vector2 position) 130 | { 131 | OnPositionChanged(oldPosition, position); 132 | PositionChanged?.Invoke(this, oldPosition, position); 133 | } 134 | 135 | protected virtual void OnPositionChanged(Vector2 oldPosition, Vector2 position) 136 | { 137 | } 138 | 139 | private void OnSizeChangedInternal(Vector2 oldSize, Vector2 size) 140 | { 141 | OnSizeChanged(oldSize, size); 142 | SizeChanged?.Invoke(this, oldSize, size); 143 | } 144 | 145 | protected virtual void OnSizeChanged(Vector2 oldSize, Vector2 size) 146 | { 147 | } 148 | 149 | private void OnViewportChangedInternal(uint oldViewportId, uint viewportId) 150 | { 151 | OnViewportChanged(oldViewportId, viewportId); 152 | ViewportChanged?.Invoke(this, oldViewportId, viewportId); 153 | } 154 | 155 | protected virtual void OnViewportChanged(uint oldViewportId, uint viewportId) 156 | { 157 | } 158 | 159 | private void OnClosedInternal() 160 | { 161 | bool onClosedHandled = false; 162 | OnClosed(ref onClosedHandled); 163 | IsShown = true; 164 | if (!onClosedHandled) 165 | { 166 | Close(); 167 | } 168 | } 169 | 170 | protected virtual void OnClosed(ref bool handled) 171 | { 172 | } 173 | 174 | public abstract void DrawContent(); 175 | 176 | protected void EndWindow() 177 | { 178 | if (!IsShown) return; 179 | ImGui.End(); 180 | windowEnded = true; 181 | } 182 | 183 | public virtual void Show() 184 | { 185 | if (IsShown) return; 186 | IsShown = true; 187 | WidgetManager.Register(this); 188 | } 189 | 190 | public virtual void Close() 191 | { 192 | if (!IsShown) return; 193 | WidgetManager.Unregister(this); 194 | IsShown = false; 195 | } 196 | 197 | public virtual void Dispose() 198 | { 199 | } 200 | } 201 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/ImageHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using System.Numerics; 5 | 6 | /// 7 | /// A helper class for working with ImGui images 8 | /// 9 | public static class ImageHelper 10 | { 11 | /// 12 | /// Displays an image centered vertically in the window with the specified size. 13 | /// 14 | /// The identifier of the image to display. 15 | /// The size of the image. 16 | public static void ImageCenteredV(ImTextureID image, Vector2 size) 17 | { 18 | var windowHeight = ImGui.GetWindowSize().Y; 19 | var imageHeight = size.Y; 20 | 21 | ImGui.SetCursorPosY((windowHeight - imageHeight) * 0.5f); 22 | ImGui.Image(image, size); 23 | } 24 | 25 | /// 26 | /// Displays an image centered horizontally in the window with the specified size. 27 | /// 28 | /// The identifier of the image to display. 29 | /// The size of the image. 30 | public static void ImageCenteredH(ImTextureID image, Vector2 size) 31 | { 32 | var windowWidth = ImGui.GetWindowSize().X; 33 | var imageWidth = size.X; 34 | 35 | ImGui.SetCursorPosX((windowWidth - imageWidth) * 0.5f); 36 | ImGui.Image(image, size); 37 | } 38 | 39 | /// 40 | /// Displays an image centered both vertically and horizontally in the window with the specified size. 41 | /// 42 | /// The identifier of the image to display. 43 | /// The size of the image. 44 | public static void ImageCenteredVH(ImTextureID image, Vector2 size) 45 | { 46 | var windowSize = ImGui.GetWindowSize(); 47 | 48 | ImGui.SetCursorPos((windowSize - size) * 0.5f); 49 | ImGui.Image(image, size); 50 | } 51 | 52 | /// 53 | /// Displays an image centered both vertically and horizontally in the window with the specified size. 54 | /// 55 | /// The identifier of the image to display. 56 | /// The size of the image. 57 | public static void ImageScaleTo(ImTextureID image, Vector2 imgSize, Vector2 destSize) 58 | { 59 | Vector2 ratio = destSize / imgSize; 60 | var scale = Math.Min(ratio.X, ratio.Y); 61 | var newSize = imgSize * scale; 62 | 63 | ImGui.Image(image, newSize); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/MessageBoxResult.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | /// 4 | /// Represents the different results or choices in a message box. 5 | /// 6 | public enum MessageBoxResult 7 | { 8 | /// 9 | /// No specific result; typically used when no choice is made. 10 | /// 11 | None, 12 | 13 | /// 14 | /// The "OK" button or choice in a message box. 15 | /// 16 | Ok, 17 | 18 | /// 19 | /// The "Cancel" button or choice in a message box. 20 | /// 21 | Cancel, 22 | 23 | /// 24 | /// The "Yes" button or choice in a message box. 25 | /// 26 | Yes, 27 | 28 | /// 29 | /// The "No" button or choice in a message box. 30 | /// 31 | No, 32 | } 33 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/MessageBoxType.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | /// 4 | /// Represents the different types of message boxes. 5 | /// 6 | public enum MessageBoxType 7 | { 8 | /// 9 | /// An "OK" button only message box. 10 | /// 11 | Ok, 12 | 13 | /// 14 | /// A message box with "OK" and "Cancel" buttons. 15 | /// 16 | OkCancel, 17 | 18 | /// 19 | /// A message box with "Yes" and "Cancel" buttons. 20 | /// 21 | YesCancel, 22 | 23 | /// 24 | /// A message box with "Yes" and "No" buttons. 25 | /// 26 | YesNo, 27 | 28 | /// 29 | /// A message box with "Yes," "No," and "Cancel" buttons. 30 | /// 31 | YesNoCancel, 32 | } 33 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/MessageBoxes.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using System.Collections.Generic; 4 | 5 | /// 6 | /// A static class responsible for managing and displaying message boxes. Fully thread-safe. 7 | /// 8 | public static class MessageBoxes 9 | { 10 | private static readonly List messageBoxes = []; 11 | 12 | /// 13 | /// Draws and updates all the open message boxes. 14 | /// 15 | public static void Draw() 16 | { 17 | lock (messageBoxes) 18 | { 19 | for (int i = 0; i < messageBoxes.Count; i++) 20 | { 21 | MessageBox box = messageBoxes[i]; 22 | if (box.Draw()) 23 | { 24 | messageBoxes.RemoveAt(i); 25 | i--; 26 | } 27 | } 28 | } 29 | } 30 | 31 | /// 32 | /// Shows a message box to be displayed and managed by the MessageBoxes class. 33 | /// 34 | /// The MessageBox instance to be displayed. 35 | public static void Show(MessageBox messageBox) 36 | { 37 | lock (messageBoxes) 38 | { 39 | messageBoxes.Add(messageBox); 40 | } 41 | } 42 | 43 | /// 44 | /// Closes a specific message box by its title. 45 | /// 46 | /// The title of the message box to be closed. 47 | /// True if the message box was found and closed; otherwise, false. 48 | public static bool Close(string title) 49 | { 50 | lock (messageBoxes) 51 | { 52 | for (int i = 0; i < messageBoxes.Count; i++) 53 | { 54 | var box = messageBoxes[i]; 55 | if (box.Title == title) 56 | { 57 | messageBoxes.RemoveAt(i); 58 | return true; 59 | } 60 | } 61 | } 62 | return false; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/Text/StrBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets.Text 2 | { 3 | using Hexa.NET.Utilities.Text; 4 | using System; 5 | 6 | public static unsafe class StrBuilderExtensions 7 | { 8 | public static StrBuilder BuildLabel(this ref StrBuilder builder, char icon, string text) 9 | { 10 | builder.Reset(); 11 | builder.Append(icon); 12 | builder.Append(text); 13 | builder.End(); 14 | return builder; 15 | } 16 | 17 | public static StrBuilder BuildLabel(this ref StrBuilder builder, char icon, ReadOnlySpan text) 18 | { 19 | builder.Reset(); 20 | builder.Append(icon); 21 | builder.Append(text); 22 | builder.End(); 23 | return builder; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/TextHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | 5 | /// 6 | /// A text helper for ImGui. 7 | /// 8 | public static class TextHelper 9 | { 10 | /// 11 | /// Centers a text vertically. 12 | /// 13 | /// The text. 14 | public static void TextCenteredV(string text) 15 | { 16 | var windowHeight = ImGui.GetWindowSize().Y; 17 | var textHeight = ImGui.CalcTextSize(text).Y; 18 | 19 | ImGui.SetCursorPosY((windowHeight - textHeight) * 0.5f); 20 | ImGui.Text(text); 21 | } 22 | 23 | /// 24 | /// Centers a text horizontally. 25 | /// 26 | /// The text. 27 | public static void TextCenteredH(string text) 28 | { 29 | var windowWidth = ImGui.GetWindowSize().X; 30 | var textWidth = ImGui.CalcTextSize(text).X; 31 | 32 | ImGui.SetCursorPosX((windowWidth - textWidth) * 0.5f); 33 | ImGui.Text(text); 34 | } 35 | 36 | /// 37 | /// Centers a text vertically and horizontally. 38 | /// 39 | /// The text. 40 | public static void TextCenteredVH(string text) 41 | { 42 | var windowSize = ImGui.GetWindowSize(); 43 | var textSize = ImGui.CalcTextSize(text); 44 | 45 | ImGui.SetCursorPos((windowSize - textSize) * 0.5f); 46 | ImGui.Text(text); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/TooltipHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | 5 | /// 6 | /// A tooltip helper for ImGui 7 | /// 8 | public static class TooltipHelper 9 | { 10 | /// 11 | /// Shows a tooltip if the item is hovered with as text. 12 | /// 13 | /// The text of the tooltip. 14 | public static void Tooltip(string desc) 15 | { 16 | if (ImGui.IsItemHovered(ImGuiHoveredFlags.DelayShort) && ImGui.BeginTooltip()) 17 | { 18 | ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); 19 | ImGui.TextUnformatted(desc); 20 | ImGui.PopTextWrapPos(); 21 | ImGui.EndTooltip(); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/TryUtils.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | 6 | internal static class TryUtils 7 | { 8 | public static TryCapture Try(this T data, Action action) 9 | { 10 | try 11 | { 12 | action(data); 13 | } 14 | catch (Exception ex) 15 | { 16 | return new TryCapture(data, ex); 17 | } 18 | 19 | return default; 20 | } 21 | 22 | public static TryCapture TryReturn(this T data, Func action) 23 | { 24 | try 25 | { 26 | action(data); 27 | } 28 | catch (Exception ex) 29 | { 30 | return new(data, default, ex); 31 | } 32 | 33 | return new(data, default, null); 34 | } 35 | 36 | public static TryCapture Try(Action action, T data) 37 | { 38 | try 39 | { 40 | action(data); 41 | } 42 | catch (Exception ex) 43 | { 44 | return new TryCapture(data, ex); 45 | } 46 | 47 | return default; 48 | } 49 | 50 | public readonly struct TryCapture 51 | { 52 | private readonly T value; 53 | private readonly Exception? exception; 54 | 55 | public TryCapture(T value, Exception? exception) 56 | { 57 | this.value = value; 58 | this.exception = exception; 59 | } 60 | 61 | public readonly void Catch(Action callback) 62 | { 63 | if (exception == null) return; 64 | callback(value, exception); 65 | } 66 | 67 | public readonly bool Failed => exception != null; 68 | public readonly bool Success => exception == null; 69 | } 70 | 71 | public readonly struct TryCapture 72 | { 73 | private readonly T value; 74 | private readonly TReturn @return; 75 | private readonly Exception? exception; 76 | 77 | public TryCapture(T value, TReturn @return, Exception? exception) 78 | { 79 | this.value = value; 80 | this.@return = @return; 81 | this.exception = exception; 82 | } 83 | 84 | public readonly TryCapture Catch(Action callback) 85 | { 86 | if (exception == null) return this; 87 | callback(value, exception); 88 | return this; 89 | } 90 | 91 | public readonly TryCapture LogFail(string message) 92 | { 93 | if (exception == null) return this; 94 | #if DEBUG 95 | Trace.WriteLine(message); 96 | #endif 97 | return this; 98 | } 99 | 100 | public readonly bool Failed => exception != null; 101 | 102 | public readonly bool Success => exception == null; 103 | 104 | public static implicit operator TReturn(TryCapture capture) => capture.@return; 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/WidgetManager.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.ImGui.Widgets.Dialogs; 5 | using System.Numerics; 6 | 7 | public static class WidgetManager 8 | { 9 | private static bool initialized; 10 | private static readonly List widgets = new(); 11 | 12 | static WidgetManager() 13 | { 14 | } 15 | 16 | public static bool BlockInput { get; set; } 17 | 18 | public static uint DockSpaceId { get; private set; } 19 | 20 | public static WidgetStyle Style { get; set; } = new(); 21 | 22 | public static IReadOnlyList Widgets => widgets; 23 | 24 | public static T? Find(string? id = null) where T : IImGuiWindow 25 | { 26 | foreach (var widget in widgets) 27 | { 28 | if (id != null && widget.Name != id) continue; 29 | if (widget is T t) 30 | { 31 | return t; 32 | } 33 | } 34 | return default; 35 | } 36 | 37 | internal static bool Register(bool mainWindow = false) where T : IImGuiWindow, new() 38 | { 39 | return Register(new T(), mainWindow); 40 | } 41 | 42 | internal static void Unregister() where T : IImGuiWindow, new() 43 | { 44 | IImGuiWindow? window = widgets.FirstOrDefault(x => x is T); 45 | if (window != null) 46 | { 47 | Unregister(window); 48 | } 49 | } 50 | 51 | internal static void Unregister(IImGuiWindow window) 52 | { 53 | if (initialized) 54 | { 55 | window.Dispose(); 56 | } 57 | 58 | widgets.Remove(window); 59 | } 60 | 61 | internal static bool Register(IImGuiWindow widget, bool mainWindow = false) 62 | { 63 | if (widgets.Count == 0) 64 | { 65 | widget.IsEmbedded = true; 66 | } 67 | 68 | if (mainWindow) 69 | { 70 | widget.IsEmbedded = true; 71 | for (int i = 0; i < widgets.Count; i++) 72 | { 73 | widgets[i].IsEmbedded = false; 74 | } 75 | } 76 | 77 | if (!initialized) 78 | { 79 | widgets.Add(widget); 80 | return false; 81 | } 82 | else 83 | { 84 | widget.Init(); 85 | widgets.Add(widget); 86 | return true; 87 | } 88 | } 89 | 90 | public static void Init() 91 | { 92 | for (int i = 0; i < widgets.Count; i++) 93 | { 94 | var widget = widgets[i]; 95 | widget.Init(); 96 | } 97 | ImGuiGC.Init(); 98 | initialized = true; 99 | } 100 | 101 | public static void Draw() 102 | { 103 | ImGui.PushStyleColor(ImGuiCol.WindowBg, Vector4.Zero); 104 | DockSpaceId = ImGui.DockSpaceOverViewport(null, ImGuiDockNodeFlags.PassthruCentralNode, null); // passing null as first argument will use the main viewport 105 | ImGui.PopStyleColor(1); 106 | 107 | ImGui.BeginDisabled(BlockInput); 108 | 109 | ImGuiWindowFlags overwriteFlags = ImGuiWindowFlags.None; 110 | if (BlockInput) 111 | { 112 | overwriteFlags |= ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoMouseInputs | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoBringToFrontOnFocus; 113 | } 114 | 115 | for (int i = 0; i < widgets.Count; i++) 116 | { 117 | widgets[i].DrawWindow(overwriteFlags); 118 | } 119 | 120 | ImGui.EndDisabled(); 121 | 122 | DialogManager.Draw(); 123 | MessageBoxes.Draw(); 124 | PopupManager.Draw(); 125 | AnimationManager.Tick(); 126 | } 127 | 128 | public static void Dispose() 129 | { 130 | AnimationManager.Release(); 131 | for (int i = 0; i < widgets.Count; i++) 132 | { 133 | widgets[i].Dispose(); 134 | } 135 | widgets.Clear(); 136 | ImGuiGC.Shutdown(); 137 | initialized = false; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /Hexa.NET.ImGui.Widgets/WidgetStyle.cs: -------------------------------------------------------------------------------- 1 | namespace Hexa.NET.ImGui.Widgets 2 | { 3 | using Hexa.NET.Utilities.Text; 4 | using System.Numerics; 5 | 6 | public class WidgetStyle 7 | { 8 | public WidgetIcon HomeIcon = MaterialIcons.Home; 9 | 10 | public WidgetIcon BackIcon = MaterialIcons.ArrowBack; 11 | 12 | public WidgetIcon ForwardIcon = MaterialIcons.ArrowForward; 13 | 14 | public WidgetIcon RefreshIcon = MaterialIcons.Refresh; 15 | 16 | public WidgetIcon CloseIcon = MaterialIcons.Close; 17 | 18 | public WidgetIcon MinimizeIcon = MaterialIcons.Minimize; 19 | 20 | public WidgetIcon FolderIcon = MaterialIcons.Folder; 21 | 22 | public WidgetIcon FileIcon = MaterialIcons.UnknownDocument; 23 | 24 | public WidgetIcon ComputerIcon = MaterialIcons.Computer; 25 | } 26 | 27 | public enum IconType 28 | { 29 | Text, 30 | Image 31 | } 32 | 33 | public unsafe struct WidgetIcon 34 | { 35 | public char IconText; 36 | public uint IconUTF8Bytes; 37 | public ImTextureID IconImage; 38 | public IconType Type; 39 | 40 | public WidgetIcon(char iconText) 41 | { 42 | IconText = iconText; 43 | uint utf8Bytes = 0; 44 | Utf8Formatter.ConvertUtf16ToUtf8(&iconText, 1, (byte*)&utf8Bytes, 4); 45 | IconUTF8Bytes = utf8Bytes; 46 | IconImage = 0; 47 | Type = IconType.Text; 48 | } 49 | 50 | public WidgetIcon(ImTextureID iconImage) 51 | { 52 | IconText = '\0'; 53 | IconUTF8Bytes = '\0'; 54 | IconImage = iconImage; 55 | Type = IconType.Image; 56 | } 57 | 58 | public readonly bool IsText => Type == IconType.Text; 59 | 60 | public readonly bool IsImage => Type == IconType.Image; 61 | 62 | public static implicit operator WidgetIcon(char iconText) => new(iconText); 63 | 64 | public static implicit operator WidgetIcon(ImTextureID iconImage) => new(iconImage); 65 | 66 | public static implicit operator char(WidgetIcon icon) => icon.IconText; 67 | 68 | public static implicit operator ImTextureID(WidgetIcon icon) => icon.IconImage; 69 | 70 | public readonly unsafe void Text() 71 | { 72 | uint* bytes = stackalloc uint[2] { IconUTF8Bytes, 0x0 }; 73 | ImGui.Text((byte*)bytes); 74 | } 75 | 76 | public readonly unsafe void TextColored(Vector4 color) 77 | { 78 | uint* bytes = stackalloc uint[2] { IconUTF8Bytes, 0x0 }; 79 | ImGui.TextColored(color, (byte*)bytes); 80 | } 81 | 82 | public readonly unsafe void TextDisabled() 83 | { 84 | uint* bytes = stackalloc uint[2] { IconUTF8Bytes, 0x0 }; 85 | ImGui.TextDisabled((byte*)bytes); 86 | } 87 | 88 | public readonly unsafe void TextWrapped() 89 | { 90 | uint* bytes = stackalloc uint[2] { IconUTF8Bytes, 0x0 }; 91 | ImGui.TextWrapped((byte*)bytes); 92 | } 93 | 94 | public readonly unsafe void TextUnformatted() 95 | { 96 | uint* bytes = stackalloc uint[2] { IconUTF8Bytes, 0x0 }; 97 | ImGui.TextUnformatted((byte*)bytes); 98 | } 99 | 100 | public readonly unsafe void BulletText() 101 | { 102 | uint* bytes = stackalloc uint[2] { IconUTF8Bytes, 0x0 }; 103 | ImGui.BulletText((byte*)bytes); 104 | } 105 | 106 | public readonly void Image(Vector2 size) 107 | { 108 | ImGui.Image(IconImage, size); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Juna Meinhold 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PerformanceTests/PerformanceTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PerformanceTests/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Running; 3 | using Hexa.NET.ImGui.Widgets.Text; 4 | using System.Text; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BenchmarkRunner.Run(); 11 | } 12 | } 13 | 14 | public class FormatterUTF8IntBenchmark 15 | { 16 | private const int stackSize = 2048; 17 | private uint value = 0; 18 | public static string s; 19 | private Random random = new Random(1321); 20 | 21 | [Benchmark] 22 | public unsafe void Convert() 23 | { 24 | value = (uint)(random.Next() + int.MaxValue / 2); 25 | byte* stack = stackalloc byte[stackSize]; 26 | Utf8Formatter.Format(value, stack, stackSize); 27 | 28 | stack[0] = (byte)'a'; 29 | } 30 | 31 | [Benchmark(Baseline = true)] 32 | public unsafe void ConvertDefault() 33 | { 34 | value = (uint)(random.Next() + int.MaxValue / 2); 35 | s = value.ToString(); 36 | fixed (char* ps = s) 37 | { 38 | ps[0] = 'a'; 39 | } 40 | } 41 | } 42 | 43 | public class FormatterUTF8ConvBenchmark 44 | { 45 | private const int stackSize = 2048; 46 | private const string str = "Hello World!"; 47 | 48 | [Benchmark(Baseline = true)] 49 | public unsafe void ConvertDefault() 50 | { 51 | byte* stack = stackalloc byte[stackSize]; 52 | fixed (char* c = str) 53 | Encoding.UTF8.GetBytes(c, str.Length, stack, stackSize); 54 | } 55 | 56 | [Benchmark] 57 | public unsafe void Convert() 58 | { 59 | byte* stack = stackalloc byte[stackSize]; 60 | fixed (char* c = str) 61 | Utf8Formatter.ConvertUtf16ToUtf8(c, str.Length, stack, stackSize); 62 | } 63 | } 64 | 65 | public class FormatterBenchmark 66 | { 67 | private const int stackSize = 2048; 68 | private string s; 69 | 70 | [Benchmark] 71 | public unsafe void FormatStandardDate() 72 | { 73 | byte* stack = stackalloc byte[stackSize]; 74 | DateTime time = DateTime.Now; 75 | Utf8Formatter.Format(time, stack, stackSize, "yyyy-MM-dd HH:mm:ss"); 76 | } 77 | 78 | [Benchmark] 79 | public unsafe void FormatComplexDate() 80 | { 81 | byte* stack = stackalloc byte[stackSize]; 82 | DateTime time = DateTime.Now; 83 | Utf8Formatter.Format(time, stack, stackSize, "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"); 84 | } 85 | 86 | [Benchmark] 87 | public unsafe void FormatStandardDateDefault() 88 | { 89 | DateTime time = DateTime.Now; 90 | s = time.ToString("yyyy-MM-dd HH:mm:ss"); 91 | } 92 | 93 | [Benchmark] 94 | public unsafe void FormatComplexDateDefault() 95 | { 96 | DateTime time = DateTime.Now; 97 | s = time.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK"); 98 | } 99 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImGui Widget Collection for Hexa.NET.ImGui 2 | 3 | This repository contains a variety of custom widgets and utilities designed to extend the capabilities of the [Hexa.NET.ImGui](https://github.com/HexaEngine/Hexa.NET.ImGui) library. Whether you're developing tools, applications, or games, these widgets will help you create more interactive and user-friendly interfaces. 4 | 5 | ## Table of Contents 6 | 7 | - [Installation](#installation) 8 | - [Widgets](#widgets) 9 | - [Contributing](#contributing) 10 | - [License](#license) 11 | 12 | ## Installation 13 | 14 | To use the ImGui Widget Collection in your project, install the NuGet package: 15 | 16 | 17 | ```sh 18 | dotnet add package Hexa.NET.ImGui.Widgets 19 | ``` 20 | 21 | ```sh 22 | dotnet add package Hexa.NET.ImGui.Widgets.Extras 23 | ``` 24 | 25 | ## Widgets 26 | - ImGuiBufferingBar (a simple buffering bar) 27 | - ImGuiButton 28 | - Toggle Button (a normal button, but when toggled it gets a circle around it to indicate that it' active) 29 | - Transparent Button (a normal button but without a background when not hovered) 30 | - ImGuiSpinner (a simple buffering spinner) 31 | - ImGuiTreeNode (a special tree node with the ability to add a icon with a color) 32 | - ImGuiWidgetFlameGraph (a widget used for drawing a flame graph) 33 | - MessageBox (a message box framework for simple yes no cancel ok dialogs) 34 | - DialogMessageBox (similar to MessageBox, but with the difference that it blocks other windows) 35 | - OpenFileDialog (used for users to select files or folders, multi selection supported) 36 | - RenameFileDialog (used for renaming files and folders) 37 | - SaveFileDialog (used for selecting a path for a file/folder to save to.) 38 | 39 | ## Helper classes and types 40 | - ComboHelper (a small helper for using enums with combo boxes) 41 | - ImageHelper (a small helper for aligning a image) 42 | - TextHelper (a helper for text alignment) 43 | - TooltipHelper (a helper for tooltips) 44 | - ImGuiName (a struct that provides a unique name useful for cases where multiple things are named the same way) 45 | 46 | ## Widgets Extra (Requires my math lib) 47 | - ImGuiBezierWidget (a widget for visually modifying bezier curves) 48 | - ImGuiCurveEditor (a curve editor commonly used in color grading) 49 | 50 | ## Contributing 51 | 52 | We welcome contributions to the ImGui Widget Collection! If you have an idea for a new widget or an improvement to an existing one, please submit a pull request. 53 | 54 | ## License 55 | 56 | This project is licensed under the MIT License. See the LICENSE file for details. 57 | -------------------------------------------------------------------------------- /TestApp/ImGuiManager.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.ImGui.Backends.OpenGL3; 5 | using Hexa.NET.ImGui.Backends.SDL2; 6 | using Hexa.NET.ImGui.Utilities; 7 | using Hexa.NET.SDL2; 8 | using Hexa.NET.Utilities; 9 | using System; 10 | using System.Diagnostics; 11 | 12 | public class ImGuiManager 13 | { 14 | private ImGuiContextPtr guiContext; 15 | 16 | private enum ImGuiFreeTypeBuilderFlags 17 | { 18 | NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes. 19 | NoAutoHint = 1 << 1, // Disable auto-hinter. 20 | ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter. 21 | LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text. 22 | MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output. 23 | Bold = 1 << 5, // Styling: Should we artificially embolden the font? 24 | Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? 25 | Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! 26 | LoadColor = 1 << 8, // Enable FreeType color-layered glyphs 27 | Bitmap = 1 << 9 // Enable FreeType bitmap glyphs 28 | }; 29 | 30 | public unsafe ImGuiManager(Hexa.NET.SDL2.SDLWindow* window, SDLGLContext context) 31 | { 32 | // Create ImGui context 33 | guiContext = ImGui.CreateContext(null); 34 | 35 | // Set ImGui context 36 | ImGui.SetCurrentContext(guiContext); 37 | 38 | // Setup ImGui config. 39 | var io = ImGui.GetIO(); 40 | io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; // Enable Keyboard Controls 41 | io.ConfigFlags |= ImGuiConfigFlags.NavEnableGamepad; // Enable Gamepad Controls 42 | io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; // Enable Docking 43 | io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; // Enable Multi-Viewport / Platform Windows 44 | io.ConfigViewportsNoAutoMerge = false; 45 | io.ConfigViewportsNoTaskBarIcon = false; 46 | io.ConfigDebugIsDebuggerPresent = Debugger.IsAttached; 47 | io.ConfigErrorRecoveryEnableAssert = true; 48 | 49 | // Setup Platform 50 | ImGuiImplSDL2.SetCurrentContext(guiContext); 51 | ImGuiImplSDL2.InitForOpenGL((Hexa.NET.ImGui.Backends.SDL2.SDLWindow*)window, (void*)context.Handle); 52 | Program.RegisterHook(HookCallback); 53 | 54 | ImGuiFontBuilder builder = new(ImGui.GetIO().Fonts); 55 | 56 | var range = io.Fonts.GetGlyphRangesDefault(); 57 | int end = 0; 58 | while (range[end] != 0) end++; 59 | 60 | var array = Utils.ToManaged(range, end)!; 61 | Array.Resize(ref array, array.Length + 2); 62 | array[^2] = 0x2200; 63 | array[^1] = 0x22FF; 64 | 65 | builder.SetOption(config => { config.PixelSnapH = true; config.OversampleH = 2; config.OversampleV = 2; }); 66 | builder.AddFontFromFileTTF("assets/fonts/arialuni.ttf", 18, array); 67 | builder.SetOption(config => 68 | { 69 | config.GlyphMinAdvanceX = 18; 70 | config.GlyphOffset = new(0, 4); 71 | config.MergeMode = true; 72 | }); 73 | builder.AddFontFromFileTTF("assets/fonts/MaterialSymbolsRounded.ttf", 16.0f, [0xe003, 0xF8FF]) 74 | .Build(); 75 | 76 | // Setup Renderer 77 | ImGuiImplOpenGL3.SetCurrentContext(guiContext); 78 | ImGuiImplOpenGL3.Init((byte*)null); 79 | } 80 | 81 | private static unsafe bool HookCallback(Hexa.NET.SDL2.SDLEvent @event) 82 | { 83 | return ImGuiImplSDL2.ProcessEvent((Hexa.NET.ImGui.Backends.SDL2.SDLEvent*)&@event); 84 | } 85 | 86 | public unsafe void NewFrame() 87 | { 88 | // Set ImGui context 89 | ImGui.SetCurrentContext(guiContext); 90 | 91 | // Start new frame, call order matters. 92 | ImGuiImplOpenGL3.NewFrame(); 93 | ImGuiImplSDL2.NewFrame(); 94 | ImGui.NewFrame(); 95 | } 96 | 97 | public static unsafe void EndFrame() 98 | { 99 | // Renders ImGui Data 100 | var io = ImGui.GetIO(); 101 | ImGui.Render(); 102 | ImGui.EndFrame(); 103 | ImGuiImplOpenGL3.RenderDrawData(ImGui.GetDrawData()); 104 | 105 | // Update and Render additional Platform Windows 106 | if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) 107 | { 108 | ImGui.UpdatePlatformWindows(); 109 | ImGui.RenderPlatformWindowsDefault(); 110 | } 111 | } 112 | 113 | public void Dispose() 114 | { 115 | ImGuiImplOpenGL3.Shutdown(); 116 | ImGuiImplSDL2.Shutdown(); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.ImGui.Widgets; 5 | using Hexa.NET.OpenGL; 6 | using Hexa.NET.SDL2; 7 | 8 | public unsafe class Program 9 | { 10 | private static bool exiting = false; 11 | private static readonly List> hooks = new(); 12 | private static SDLWindow* mainWindow; 13 | private static uint mainWindowId; 14 | 15 | private static int width; 16 | private static int height; 17 | 18 | private static ImGuiManager imGuiManager; 19 | internal static GL GL; 20 | private static SDLGLContext glcontext; 21 | 22 | public static int Width => width; 23 | 24 | public static int Height => height; 25 | 26 | public static event EventHandler? Resized; 27 | 28 | private static void Main(string[] args) 29 | { 30 | SDL.SetHint(SDL.SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); 31 | SDL.SetHint(SDL.SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); 32 | SDL.SetHint(SDL.SDL_HINT_AUTO_UPDATE_JOYSTICKS, "1"); 33 | SDL.SetHint(SDL.SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); 34 | SDL.SetHint(SDL.SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); 35 | SDL.SetHint(SDL.SDL_HINT_JOYSTICK_RAWINPUT, "0"); 36 | SDL.Init(SDL.SDL_INIT_EVERYTHING); 37 | 38 | SDL.GLSetAttribute(SDLGLattr.GlContextMajorVersion, 3); 39 | SDL.GLSetAttribute(SDLGLattr.GlContextMinorVersion, 3); 40 | SDL.GLSetAttribute(SDLGLattr.GlContextProfileMask, (int)SDLGLprofile.GlContextProfileCore); 41 | 42 | int width = 1280; 43 | int height = 720; 44 | int y = 100; 45 | int x = 100; 46 | 47 | SDLWindowFlags flags = SDLWindowFlags.Resizable | SDLWindowFlags.Hidden | SDLWindowFlags.AllowHighdpi | SDLWindowFlags.Opengl; 48 | mainWindow = SDL.CreateWindow("", x, y, width, height, (uint)flags); 49 | mainWindowId = SDL.GetWindowID(mainWindow); 50 | 51 | InitGraphics(mainWindow); 52 | InitImGui(mainWindow); 53 | 54 | SDL.ShowWindow(mainWindow); 55 | 56 | Time.Initialize(); 57 | 58 | SDLEvent evnt; 59 | while (!exiting) 60 | { 61 | SDL.PumpEvents(); 62 | while (SDL.PollEvent(&evnt) == (int)SDLBool.True) 63 | { 64 | for (int i = 0; i < hooks.Count; i++) 65 | { 66 | hooks[i](evnt); 67 | } 68 | 69 | HandleEvent(evnt); 70 | } 71 | 72 | Render(); 73 | 74 | Time.FrameUpdate(); 75 | } 76 | 77 | WidgetManager.Dispose(); 78 | imGuiManager.Dispose(); 79 | 80 | SDL.DestroyWindow(mainWindow); 81 | 82 | SDL.Quit(); 83 | } 84 | 85 | private static void Render() 86 | { 87 | imGuiManager.NewFrame(); 88 | 89 | WidgetManager.Draw(); 90 | ImGui.ShowDemoWindow(); 91 | 92 | SDL.GLMakeCurrent(mainWindow, glcontext); 93 | GL.BindFramebuffer(GLFramebufferTarget.Framebuffer, 0); 94 | GL.Clear(GLClearBufferMask.ColorBufferBit | GLClearBufferMask.DepthBufferBit); 95 | 96 | ImGuiManager.EndFrame(); 97 | 98 | SDL.GLMakeCurrent(mainWindow, glcontext); 99 | SDL.GLSwapWindow(mainWindow); 100 | } 101 | 102 | private static void InitGraphics(SDLWindow* mainWindow) 103 | { 104 | SDLNativeContext context = new(mainWindow); 105 | glcontext = context.Handle; 106 | GL = new(context); 107 | } 108 | 109 | private static void InitImGui(SDLWindow* mainWindow) 110 | { 111 | imGuiManager = new(mainWindow, glcontext); 112 | WidgetDemo demo = new(); 113 | demo.Show(); 114 | 115 | WidgetDemo2 demo2 = new(); 116 | demo2.Show(); 117 | 118 | WidgetManager.Init(); 119 | } 120 | 121 | private static void Resize(int width, int height, int oldWidth, int oldHeight) 122 | { 123 | GL.Viewport(0, 0, width, height); 124 | Resized?.Invoke(null, new(width, height, oldWidth, oldHeight)); 125 | } 126 | 127 | public static void RegisterHook(Func hook) 128 | { 129 | hooks.Add(hook); 130 | } 131 | 132 | private static void HandleEvent(SDLEvent evnt) 133 | { 134 | SDLEventType type = (SDLEventType)evnt.Type; 135 | switch (type) 136 | { 137 | case SDLEventType.Windowevent: 138 | { 139 | var even = evnt.Window; 140 | if (even.WindowID == mainWindowId) 141 | { 142 | switch ((SDLWindowEventID)evnt.Window.Event) 143 | { 144 | case SDLWindowEventID.Close: 145 | exiting = true; 146 | break; 147 | 148 | case SDLWindowEventID.Resized: 149 | int oldWidth = Program.width; 150 | int oldHeight = Program.height; 151 | int width = even.Data1; 152 | int height = even.Data2; 153 | Resize(width, height, oldWidth, oldHeight); 154 | Program.width = width; 155 | Program.height = height; 156 | break; 157 | } 158 | } 159 | } 160 | break; 161 | 162 | case SDLEventType.Dropfile: 163 | SDL.Free(evnt.Drop.File); 164 | break; 165 | } 166 | } 167 | } 168 | } -------------------------------------------------------------------------------- /TestApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "TestApp": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /TestApp/ResizedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using System; 4 | 5 | public class ResizedEventArgs : EventArgs 6 | { 7 | public ResizedEventArgs(int width, int height, int oldWidth, int oldHeight) 8 | { 9 | Width = width; 10 | Height = height; 11 | OldWidth = oldWidth; 12 | OldHeight = oldHeight; 13 | } 14 | 15 | public int Width { get; } 16 | 17 | public int Height { get; } 18 | 19 | public int OldWidth { get; } 20 | 21 | public int OldHeight { get; } 22 | } 23 | } -------------------------------------------------------------------------------- /TestApp/SDLNativeContext.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using Hexa.NET.SDL2; 4 | using HexaGen.Runtime; 5 | 6 | public unsafe class SDLNativeContext : IGLContext 7 | { 8 | private readonly SDLWindow* window; 9 | private readonly SDLGLContext context; 10 | 11 | public SDLNativeContext(SDLWindow* window) 12 | { 13 | this.window = window; 14 | context = SDL.GLCreateContext(window); 15 | } 16 | 17 | public nint Handle => context.Handle; 18 | 19 | public bool IsCurrent => SDL.GLGetCurrentContext() == context; 20 | 21 | public nint GetProcAddress(string procName) 22 | { 23 | return (nint)SDL.GLGetProcAddress(procName); 24 | } 25 | 26 | public bool IsExtensionSupported(string extensionName) 27 | { 28 | return SDL.GLExtensionSupported(extensionName) == SDLBool.True; 29 | } 30 | 31 | public bool TryGetProcAddress(string procName, out nint procAddress) 32 | { 33 | procAddress = (nint)SDL.GLGetProcAddress(procName); 34 | return procAddress != 0; 35 | } 36 | 37 | public void Dispose() 38 | { 39 | SDL.GLDeleteContext(context); 40 | } 41 | 42 | public void MakeCurrent() 43 | { 44 | SDL.GLMakeCurrent(window, context); 45 | } 46 | 47 | public void SwapBuffers() 48 | { 49 | SDL.GLSwapWindow(window); 50 | } 51 | 52 | public void SwapInterval(int interval) 53 | { 54 | SDL.GLSetSwapInterval(interval); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /TestApp/TestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0;net472 6 | enable 7 | enable 8 | true 9 | 12 10 | win-x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Always 31 | 32 | 33 | Always 34 | 35 | 36 | Always 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /TestApp/Time.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | 6 | public static class Time 7 | { 8 | private static long last; 9 | private static float fixedTime; 10 | private static float cumulativeFrameTime; 11 | 12 | // Properties 13 | public static float Delta { get; private set; } 14 | 15 | public static float CumulativeFrameTime { get => cumulativeFrameTime; } 16 | 17 | public static int FixedUpdateRate { get; set; } = 3; 18 | 19 | public static float FixedUpdatePerSecond => FixedUpdateRate / 1000F; 20 | 21 | public static event EventHandler? FixedUpdate; 22 | 23 | // Public Methods 24 | public static void Initialize() 25 | { 26 | last = Stopwatch.GetTimestamp(); 27 | fixedTime = 0; 28 | cumulativeFrameTime = 0; 29 | } 30 | 31 | public static void FrameUpdate() 32 | { 33 | long now = Stopwatch.GetTimestamp(); 34 | double deltaTime = ((double)now - last) / Stopwatch.Frequency; 35 | 36 | // Calculate the frame time by the time difference over the timer speed resolution. 37 | Delta = (float)deltaTime; 38 | cumulativeFrameTime += Delta; 39 | fixedTime += Delta; 40 | if (deltaTime == 0 || deltaTime < 0) 41 | { 42 | throw new InvalidOperationException(); 43 | } 44 | 45 | while (fixedTime > FixedUpdatePerSecond) 46 | { 47 | fixedTime -= FixedUpdatePerSecond; 48 | FixedUpdate?.Invoke(null, EventArgs.Empty); 49 | } 50 | 51 | last = now; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /TestApp/assets/fonts/MaterialSymbolsRounded.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TestApp/assets/fonts/MaterialSymbolsRounded.ttf -------------------------------------------------------------------------------- /TestApp/assets/fonts/arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TestApp/assets/fonts/arial.ttf -------------------------------------------------------------------------------- /TestApp/assets/fonts/arialuni.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TestApp/assets/fonts/arialuni.TTF -------------------------------------------------------------------------------- /TestApp/test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TestApp/test.txt -------------------------------------------------------------------------------- /TextEditor/ImGuiManager.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.ImGui.Backends.OpenGL3; 5 | using Hexa.NET.ImGui.Backends.SDL2; 6 | using Hexa.NET.ImGui.Utilities; 7 | using Hexa.NET.SDL2; 8 | using Hexa.NET.Utilities; 9 | using System; 10 | using System.Diagnostics; 11 | 12 | public class ImGuiManager 13 | { 14 | private ImGuiContextPtr guiContext; 15 | 16 | private enum ImGuiFreeTypeBuilderFlags 17 | { 18 | NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes. 19 | NoAutoHint = 1 << 1, // Disable auto-hinter. 20 | ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter. 21 | LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text. 22 | MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output. 23 | Bold = 1 << 5, // Styling: Should we artificially embolden the font? 24 | Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? 25 | Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! 26 | LoadColor = 1 << 8, // Enable FreeType color-layered glyphs 27 | Bitmap = 1 << 9 // Enable FreeType bitmap glyphs 28 | }; 29 | 30 | public unsafe ImGuiManager(Hexa.NET.SDL2.SDLWindow* window, SDLGLContext context) 31 | { 32 | // Create ImGui context 33 | guiContext = ImGui.CreateContext(null); 34 | 35 | // Set ImGui context 36 | ImGui.SetCurrentContext(guiContext); 37 | 38 | // Setup ImGui config. 39 | var io = ImGui.GetIO(); 40 | io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; // Enable Keyboard Controls 41 | io.ConfigFlags |= ImGuiConfigFlags.NavEnableGamepad; // Enable Gamepad Controls 42 | io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; // Enable Docking 43 | io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; // Enable Multi-Viewport / Platform Windows 44 | io.ConfigViewportsNoAutoMerge = false; 45 | io.ConfigViewportsNoTaskBarIcon = false; 46 | io.ConfigDebugIsDebuggerPresent = Debugger.IsAttached; 47 | io.ConfigErrorRecoveryEnableAssert = true; 48 | 49 | // Setup Platform 50 | ImGuiImplSDL2.SetCurrentContext(guiContext); 51 | ImGuiImplSDL2.InitForOpenGL((Hexa.NET.ImGui.Backends.SDL2.SDLWindow*)window, (void*)context.Handle); 52 | Program.RegisterHook(HookCallback); 53 | 54 | ImGuiFontBuilder builder = new(ImGui.GetIO().Fonts); 55 | 56 | var range = io.Fonts.GetGlyphRangesDefault(); 57 | int end = 0; 58 | while (range[end] != 0) end++; 59 | 60 | var array = Utils.ToManaged(range, end)!; 61 | Array.Resize(ref array, array.Length + 2); 62 | array[^2] = 0x2200; 63 | array[^1] = 0x22FF; 64 | 65 | builder.SetOption(config => { config.PixelSnapH = true; config.OversampleH = 2; config.OversampleV = 2; }); 66 | builder.AddFontFromFileTTF("assets/fonts/arialuni.ttf", 18, array); 67 | builder.SetOption(config => 68 | { 69 | config.GlyphMinAdvanceX = 18; 70 | config.GlyphOffset = new(0, 4); 71 | config.MergeMode = true; 72 | }); 73 | builder.AddFontFromFileTTF("assets/fonts/MaterialSymbolsRounded.ttf", 16.0f, [0xe003, 0xF8FF]) 74 | .Build(); 75 | 76 | // Setup Renderer 77 | ImGuiImplOpenGL3.SetCurrentContext(guiContext); 78 | ImGuiImplOpenGL3.Init((byte*)null); 79 | } 80 | 81 | private static unsafe bool HookCallback(Hexa.NET.SDL2.SDLEvent @event) 82 | { 83 | return ImGuiImplSDL2.ProcessEvent((Hexa.NET.ImGui.Backends.SDL2.SDLEvent*)&@event); 84 | } 85 | 86 | public unsafe void NewFrame() 87 | { 88 | // Set ImGui context 89 | ImGui.SetCurrentContext(guiContext); 90 | 91 | // Start new frame, call order matters. 92 | ImGuiImplOpenGL3.NewFrame(); 93 | ImGuiImplSDL2.NewFrame(); 94 | ImGui.NewFrame(); 95 | } 96 | 97 | public static unsafe void EndFrame() 98 | { 99 | // Renders ImGui Data 100 | var io = ImGui.GetIO(); 101 | ImGui.Render(); 102 | ImGui.EndFrame(); 103 | ImGuiImplOpenGL3.RenderDrawData(ImGui.GetDrawData()); 104 | 105 | // Update and Render additional Platform Windows 106 | if ((io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0) 107 | { 108 | ImGui.UpdatePlatformWindows(); 109 | ImGui.RenderPlatformWindowsDefault(); 110 | } 111 | } 112 | 113 | public void Dispose() 114 | { 115 | ImGuiImplOpenGL3.Shutdown(); 116 | ImGuiImplSDL2.Shutdown(); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /TextEditor/Program.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using Hexa.NET.ImGui; 4 | using Hexa.NET.ImGui.Widgets; 5 | using Hexa.NET.ImGui.Widgets.Extras.TextEditor; 6 | using Hexa.NET.OpenGL; 7 | using Hexa.NET.SDL2; 8 | 9 | public unsafe class Program 10 | { 11 | private static bool exiting = false; 12 | private static readonly List> hooks = new(); 13 | private static SDLWindow* mainWindow; 14 | private static uint mainWindowId; 15 | 16 | private static int width; 17 | private static int height; 18 | 19 | private static ImGuiManager imGuiManager; 20 | internal static GL GL; 21 | private static SDLGLContext glcontext; 22 | 23 | public static int Width => width; 24 | 25 | public static int Height => height; 26 | 27 | public static event EventHandler? Resized; 28 | 29 | private static void Main(string[] args) 30 | { 31 | SDL.SetHint(SDL.SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); 32 | SDL.SetHint(SDL.SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); 33 | SDL.SetHint(SDL.SDL_HINT_AUTO_UPDATE_JOYSTICKS, "1"); 34 | SDL.SetHint(SDL.SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); 35 | SDL.SetHint(SDL.SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); 36 | SDL.SetHint(SDL.SDL_HINT_JOYSTICK_RAWINPUT, "0"); 37 | SDL.Init(SDL.SDL_INIT_EVERYTHING); 38 | 39 | SDL.GLSetAttribute(SDLGLattr.GlContextMajorVersion, 3); 40 | SDL.GLSetAttribute(SDLGLattr.GlContextMinorVersion, 3); 41 | SDL.GLSetAttribute(SDLGLattr.GlContextProfileMask, (int)SDLGLprofile.GlContextProfileCore); 42 | 43 | int width = 1280; 44 | int height = 720; 45 | int y = 100; 46 | int x = 100; 47 | 48 | SDLWindowFlags flags = SDLWindowFlags.Resizable | SDLWindowFlags.Hidden | SDLWindowFlags.AllowHighdpi | SDLWindowFlags.Opengl; 49 | mainWindow = SDL.CreateWindow("", x, y, width, height, (uint)flags); 50 | mainWindowId = SDL.GetWindowID(mainWindow); 51 | 52 | InitGraphics(mainWindow); 53 | InitImGui(mainWindow); 54 | 55 | SDL.ShowWindow(mainWindow); 56 | 57 | Time.Initialize(); 58 | 59 | SDLEvent evnt; 60 | while (!exiting) 61 | { 62 | SDL.PumpEvents(); 63 | while (SDL.PollEvent(&evnt) == (int)SDLBool.True) 64 | { 65 | for (int i = 0; i < hooks.Count; i++) 66 | { 67 | hooks[i](evnt); 68 | } 69 | 70 | HandleEvent(evnt); 71 | } 72 | 73 | Render(); 74 | 75 | Time.FrameUpdate(); 76 | } 77 | 78 | WidgetManager.Dispose(); 79 | imGuiManager.Dispose(); 80 | 81 | SDL.DestroyWindow(mainWindow); 82 | 83 | SDL.Quit(); 84 | } 85 | 86 | private static void Render() 87 | { 88 | imGuiManager.NewFrame(); 89 | 90 | WidgetManager.Draw(); 91 | 92 | SDL.GLMakeCurrent(mainWindow, glcontext); 93 | GL.BindFramebuffer(GLFramebufferTarget.Framebuffer, 0); 94 | GL.Clear(GLClearBufferMask.ColorBufferBit | GLClearBufferMask.DepthBufferBit); 95 | 96 | ImGuiManager.EndFrame(); 97 | 98 | SDL.GLMakeCurrent(mainWindow, glcontext); 99 | SDL.GLSwapWindow(mainWindow); 100 | } 101 | 102 | private static void InitGraphics(SDLWindow* mainWindow) 103 | { 104 | SDLNativeContext context = new(mainWindow); 105 | glcontext = context.Handle; 106 | GL = new(context); 107 | } 108 | 109 | private static void InitImGui(SDLWindow* mainWindow) 110 | { 111 | imGuiManager = new(mainWindow, glcontext); 112 | 113 | TextEditorWindow window = new(); 114 | window.Show(); 115 | 116 | WidgetManager.Init(); 117 | } 118 | 119 | private static void Resize(int width, int height, int oldWidth, int oldHeight) 120 | { 121 | GL.Viewport(0, 0, width, height); 122 | Resized?.Invoke(null, new(width, height, oldWidth, oldHeight)); 123 | } 124 | 125 | public static void RegisterHook(Func hook) 126 | { 127 | hooks.Add(hook); 128 | } 129 | 130 | private static void HandleEvent(SDLEvent evnt) 131 | { 132 | SDLEventType type = (SDLEventType)evnt.Type; 133 | switch (type) 134 | { 135 | case SDLEventType.Windowevent: 136 | { 137 | var even = evnt.Window; 138 | if (even.WindowID == mainWindowId) 139 | { 140 | switch ((SDLWindowEventID)evnt.Window.Event) 141 | { 142 | case SDLWindowEventID.Close: 143 | exiting = true; 144 | break; 145 | 146 | case SDLWindowEventID.Resized: 147 | int oldWidth = Program.width; 148 | int oldHeight = Program.height; 149 | int width = even.Data1; 150 | int height = even.Data2; 151 | Resize(width, height, oldWidth, oldHeight); 152 | Program.width = width; 153 | Program.height = height; 154 | break; 155 | } 156 | } 157 | } 158 | break; 159 | 160 | case SDLEventType.Dropfile: 161 | SDL.Free(evnt.Drop.File); 162 | break; 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /TextEditor/ResizedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using System; 4 | 5 | public class ResizedEventArgs : EventArgs 6 | { 7 | public ResizedEventArgs(int width, int height, int oldWidth, int oldHeight) 8 | { 9 | Width = width; 10 | Height = height; 11 | OldWidth = oldWidth; 12 | OldHeight = oldHeight; 13 | } 14 | 15 | public int Width { get; } 16 | 17 | public int Height { get; } 18 | 19 | public int OldWidth { get; } 20 | 21 | public int OldHeight { get; } 22 | } 23 | } -------------------------------------------------------------------------------- /TextEditor/SDLNativeContext.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using Hexa.NET.SDL2; 4 | using HexaGen.Runtime; 5 | 6 | public unsafe class SDLNativeContext : IGLContext 7 | { 8 | private readonly SDLWindow* window; 9 | private readonly SDLGLContext context; 10 | 11 | public SDLNativeContext(SDLWindow* window) 12 | { 13 | this.window = window; 14 | context = SDL.GLCreateContext(window); 15 | } 16 | 17 | public nint Handle => context.Handle; 18 | 19 | public bool IsCurrent => SDL.GLGetCurrentContext() == context; 20 | 21 | public nint GetProcAddress(string procName) 22 | { 23 | return (nint)SDL.GLGetProcAddress(procName); 24 | } 25 | 26 | public bool IsExtensionSupported(string extensionName) 27 | { 28 | return SDL.GLExtensionSupported(extensionName) == SDLBool.True; 29 | } 30 | 31 | public bool TryGetProcAddress(string procName, out nint procAddress) 32 | { 33 | procAddress = (nint)SDL.GLGetProcAddress(procName); 34 | return procAddress != 0; 35 | } 36 | 37 | public void Dispose() 38 | { 39 | SDL.GLDeleteContext(context); 40 | } 41 | 42 | public void MakeCurrent() 43 | { 44 | SDL.GLMakeCurrent(window, context); 45 | } 46 | 47 | public void SwapBuffers() 48 | { 49 | SDL.GLSwapWindow(window); 50 | } 51 | 52 | public void SwapInterval(int interval) 53 | { 54 | SDL.GLSetSwapInterval(interval); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /TextEditor/TextEditor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0 6 | enable 7 | enable 8 | true 9 | 12 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | Always 28 | 29 | 30 | Always 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /TextEditor/Time.cs: -------------------------------------------------------------------------------- 1 | namespace TestApp 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | 6 | public static class Time 7 | { 8 | private static long last; 9 | private static float fixedTime; 10 | private static float cumulativeFrameTime; 11 | 12 | // Properties 13 | public static float Delta { get; private set; } 14 | 15 | public static float CumulativeFrameTime { get => cumulativeFrameTime; } 16 | 17 | public static int FixedUpdateRate { get; set; } = 3; 18 | 19 | public static float FixedUpdatePerSecond => FixedUpdateRate / 1000F; 20 | 21 | public static event EventHandler? FixedUpdate; 22 | 23 | // Public Methods 24 | public static void Initialize() 25 | { 26 | last = Stopwatch.GetTimestamp(); 27 | fixedTime = 0; 28 | cumulativeFrameTime = 0; 29 | } 30 | 31 | public static void FrameUpdate() 32 | { 33 | long now = Stopwatch.GetTimestamp(); 34 | double deltaTime = ((double)now - last) / Stopwatch.Frequency; 35 | 36 | // Calculate the frame time by the time difference over the timer speed resolution. 37 | Delta = (float)deltaTime; 38 | cumulativeFrameTime += Delta; 39 | fixedTime += Delta; 40 | if (deltaTime == 0 || deltaTime < 0) 41 | { 42 | throw new InvalidOperationException(); 43 | } 44 | 45 | while (fixedTime > FixedUpdatePerSecond) 46 | { 47 | fixedTime -= FixedUpdatePerSecond; 48 | FixedUpdate?.Invoke(null, EventArgs.Empty); 49 | } 50 | 51 | last = now; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /TextEditor/assets/fonts/MaterialSymbolsRounded.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TextEditor/assets/fonts/MaterialSymbolsRounded.ttf -------------------------------------------------------------------------------- /TextEditor/assets/fonts/arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TextEditor/assets/fonts/arial.ttf -------------------------------------------------------------------------------- /TextEditor/assets/fonts/arialuni.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HexaEngine/Hexa.NET.ImGui.Widgets/e91823743465bf2a3caf5e50dfc804b088da938e/TextEditor/assets/fonts/arialuni.ttf --------------------------------------------------------------------------------