├── .gitignore ├── Iguina.Demo.MonoGame ├── .config │ └── dotnet-tools.json ├── Content │ ├── Content.mgcb │ ├── default_font.spritefont │ └── disabled_effect.fx ├── Game1.cs ├── Icon.bmp ├── Icon.ico ├── Iguina.Demo.MonoGame.csproj ├── MonoGameInput.cs ├── MonoGameRenderer.cs ├── Program.cs ├── ReadmeExample.cs └── app.manifest ├── Iguina.Demo.RayLib ├── Iguina.Demo.RayLib.csproj ├── Program.cs ├── RayLibInput.cs ├── RayLibRenderer.cs ├── ReadmeExample.cs └── readme.md ├── Iguina.Demo ├── Assets │ └── DefaultTheme │ │ ├── Styles │ │ ├── button.json │ │ ├── checkbox.json │ │ ├── color_picker.json │ │ ├── color_picker_handle.json │ │ ├── color_slider_horizontal.json │ │ ├── color_slider_horizontal_handle.json │ │ ├── dropdown_icon.json │ │ ├── horizontal_line.json │ │ ├── label.json │ │ ├── list_item.json │ │ ├── list_panel.json │ │ ├── message_box_backdrop.json │ │ ├── numeric_input.json │ │ ├── numeric_input_button.json │ │ ├── panel.json │ │ ├── panel_title.json │ │ ├── paragraph.json │ │ ├── paragraph_base_style.json │ │ ├── progress_bar_horizontal.json │ │ ├── progress_bar_horizontal_alt.json │ │ ├── progress_bar_horizontal_alt_fill.json │ │ ├── progress_bar_horizontal_fill.json │ │ ├── radio_button.json │ │ ├── scrollbar_vertical.json │ │ ├── scrollbar_vertical_handle.json │ │ ├── slider_handle.json │ │ ├── slider_horizontal.json │ │ ├── slider_vertical.json │ │ ├── text_input.json │ │ ├── title.json │ │ └── vertical_line.json │ │ ├── Textures │ │ ├── Icons.png │ │ └── UI.png │ │ └── system_style.json ├── Iguina.Demo.csproj ├── IguinaDemoStarter.cs └── readme.md ├── Iguina.Tests ├── Iguina.Tests.csproj ├── NumericInputTests.cs ├── TestInputProvider.cs └── TestRenderer.cs ├── Iguina.sln ├── Iguina ├── Defs │ ├── Anchor.cs │ ├── Color.cs │ ├── CursorProperties.cs │ ├── EntityState.cs │ ├── FramedTexture.cs │ ├── IconTexture.cs │ ├── InputState.cs │ ├── MeasureUnits.cs │ ├── Orientation.cs │ ├── OverflowMode.cs │ ├── Point.cs │ ├── Rectangle.cs │ ├── Sides.cs │ ├── StretchedTexture.cs │ ├── StyleSheet.cs │ ├── SystemStyleSheet.cs │ └── TextAlignment.cs ├── Drivers │ ├── IFilesProvider.cs │ ├── IInputProvider.cs │ └── IRenderer.cs ├── Entities │ ├── Button.cs │ ├── Checkbox.cs │ ├── CheckedEntity.cs │ ├── ColorPicker.cs │ ├── ColorSlider.cs │ ├── DropDown.cs │ ├── Entity.cs │ ├── HorizontalLine.cs │ ├── IColorPicker.cs │ ├── Label.cs │ ├── ListBox.cs │ ├── NumericInput.cs │ ├── Panel.cs │ ├── Paragraph.cs │ ├── ProgressBar.cs │ ├── RadioButton.cs │ ├── RowsSpacer.cs │ ├── Slider.cs │ ├── TextInput.cs │ ├── Title.cs │ └── VerticalLine.cs ├── Iguina.csproj ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml ├── UISystem.cs ├── Utils │ ├── DrawUtils.cs │ ├── MathUtils.cs │ └── MessageBoxUtils.cs └── readme.md ├── LICENSE ├── ReadmeAssets ├── anchors.png ├── demo.gif ├── entity-button.png ├── entity-checkbox.png ├── entity-colorpicker.png ├── entity-colorslider.png ├── entity-dropdown.png ├── entity-hl.png ├── entity-list.png ├── entity-numericinput.png ├── entity-panel.png ├── entity-paragraph.png ├── entity-progressbars.png ├── entity-sliders.png ├── entity-textinput.png ├── entity-vl.png ├── logo.png ├── logo.svg ├── logo_bg.png ├── logo_no_text.png ├── mg_basic_example.png ├── rl_basic_example.png └── save-file-dialog.png ├── TODO.txt └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Download this file using PowerShell v3 under Windows with the following comand: 2 | # Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore 3 | # or wget: 4 | # wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.sln.docstates 10 | 11 | # blender temp files 12 | *.blend1 13 | 14 | # Build results 15 | 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | x64/ 19 | build/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | 23 | # NuGet Packages 24 | *.nupkg 25 | # The packages folder can be ignored because of Package Restore 26 | **/packages/* 27 | # except build/, which is used as an MSBuild target. 28 | !**/packages/build/ 29 | # Uncomment if necessary however generally it will be regenerated when needed 30 | #!**/packages/repositories.config 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | *_i.c 37 | *_p.c 38 | *.ilk 39 | *.meta 40 | # *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.log 58 | *.scc 59 | 60 | # OS generated files # 61 | .DS_Store* 62 | Icon? 63 | 64 | # Visual C++ cache files 65 | ipch/ 66 | *.aps 67 | *.ncb 68 | *.opensdf 69 | *.sdf 70 | *.cachefile 71 | 72 | # Visual Studio profiler 73 | *.psess 74 | *.vsp 75 | *.vspx 76 | 77 | # Guidance Automation Toolkit 78 | *.gpState 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper*/ 82 | *.[Rr]e[Ss]harper 83 | 84 | # TeamCity is a build add-in 85 | _TeamCity* 86 | 87 | # DotCover is a Code Coverage Tool 88 | *.dotCover 89 | 90 | # NCrunch 91 | *.ncrunch* 92 | .*crunch*.local.xml 93 | 94 | # Installshield output folder 95 | [Ee]xpress/ 96 | 97 | # DocProject is a documentation generator add-in 98 | DocProject/buildhelp/ 99 | DocProject/Help/*.HxT 100 | DocProject/Help/*.HxC 101 | DocProject/Help/*.hhc 102 | DocProject/Help/*.hhk 103 | DocProject/Help/*.hhp 104 | DocProject/Help/Html2 105 | DocProject/Help/html 106 | 107 | # Click-Once directory 108 | publish/ 109 | 110 | # Publish Web Output 111 | *.Publish.xml 112 | 113 | # Windows Azure Build Output 114 | csx 115 | *.build.csdef 116 | 117 | # Windows Store app package directory 118 | AppPackages/ 119 | 120 | # Others 121 | *.Cache 122 | ClientBin/ 123 | [Ss]tyle[Cc]op.* 124 | ~$* 125 | *~ 126 | *.dbmdl 127 | *.[Pp]ublish.xml 128 | *.pfx 129 | *.publishsettings 130 | modulesbin/ 131 | tempbin/ 132 | 133 | # EPiServer Site file (VPP) 134 | AppData/ 135 | 136 | # RIA/Silverlight projects 137 | Generated_Code/ 138 | 139 | # Backup & report files from converting an old project file to a newer 140 | # Visual Studio version. Backup files are not needed, because we have git ;-) 141 | _UpgradeReport_Files/ 142 | Backup*/ 143 | UpgradeLog*.XML 144 | UpgradeLog*.htm 145 | 146 | # vim 147 | *.txt~ 148 | *.swp 149 | *.swo 150 | 151 | # svn 152 | .svn 153 | 154 | # Remainings from resolvings conflicts in Source Control 155 | *.orig 156 | 157 | # SQL Server files 158 | **/App_Data/*.mdf 159 | **/App_Data/*.ldf 160 | **/App_Data/*.sdf 161 | 162 | 163 | #LightSwitch generated files 164 | GeneratedArtifacts/ 165 | _Pvt_Extensions/ 166 | ModelManifest.xml 167 | 168 | # ========================= 169 | # Windows detritus 170 | # ========================= 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac desktop service store files 183 | .DS_Store 184 | 185 | # SASS Compiler cache 186 | .sass-cache 187 | 188 | # Visual Studio 2014 CTP 189 | **/*.sln.ide 190 | 191 | # Visual Studio temp something 192 | .vs/ 193 | 194 | # VS 2015+ 195 | *.vc.vc.opendb 196 | *.vc.db 197 | 198 | # Rider 199 | .idea/ 200 | 201 | # Output folder used by Webpack or other FE stuff 202 | **/node_modules/* 203 | #**/wwwroot/* 204 | 205 | # SpecFlow specific 206 | *.feature.cs 207 | *.feature.xlsx.* 208 | *.Specs_*.html 209 | 210 | ##### 211 | # End of core ignore list, below put you custom 'per project' settings (patterns or path) 212 | ##### 213 | nuget.exe 214 | -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-mgcb": { 6 | "version": "3.8.1.303", 7 | "commands": [ 8 | "mgcb" 9 | ] 10 | }, 11 | "dotnet-mgcb-editor": { 12 | "version": "3.8.1.303", 13 | "commands": [ 14 | "mgcb-editor" 15 | ] 16 | }, 17 | "dotnet-mgcb-editor-linux": { 18 | "version": "3.8.1.303", 19 | "commands": [ 20 | "mgcb-editor-linux" 21 | ] 22 | }, 23 | "dotnet-mgcb-editor-windows": { 24 | "version": "3.8.1.303", 25 | "commands": [ 26 | "mgcb-editor-windows" 27 | ] 28 | }, 29 | "dotnet-mgcb-editor-mac": { 30 | "version": "3.8.1.303", 31 | "commands": [ 32 | "mgcb-editor-mac" 33 | ] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/$(Platform) 5 | /intermediateDir:obj/$(Platform) 6 | /platform:DesktopGL 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | #begin default_font.spritefont 17 | /importer:FontDescriptionImporter 18 | /processor:FontDescriptionProcessor 19 | /processorParam:PremultiplyAlpha=True 20 | /processorParam:TextureFormat=Compressed 21 | /build:default_font.spritefont 22 | 23 | #begin disabled_effect.fx 24 | /importer:EffectImporter 25 | /processor:EffectProcessor 26 | /processorParam:DebugMode=Auto 27 | /build:disabled_effect.fx;disabled.fx 28 | 29 | -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Content/default_font.spritefont: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 11 | 14 | Arial 15 | 16 | 20 | 24 21 | 22 | 26 | 0 27 | 28 | 32 | true 33 | 34 | 38 | 39 | 40 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | ~ 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Content/disabled_effect.fx: -------------------------------------------------------------------------------- 1 | #if OPENGL 2 | #define SV_POSITION POSITION 3 | #define VS_SHADERMODEL vs_3_0 4 | #define PS_SHADERMODEL ps_3_0 5 | #else 6 | #define VS_SHADERMODEL vs_4_0_level_9_1 7 | #define PS_SHADERMODEL ps_4_0_level_9_1 8 | #endif 9 | 10 | Texture2D SpriteTexture; 11 | 12 | sampler2D SpriteTextureSampler = sampler_state 13 | { 14 | Texture = ; 15 | }; 16 | 17 | struct VertexShaderOutput 18 | { 19 | float4 Position : SV_POSITION; 20 | float4 Color : COLOR0; 21 | float2 TextureCoordinates : TEXCOORD0; 22 | }; 23 | 24 | float4 MainPS(VertexShaderOutput input) : COLOR 25 | { 26 | float4 texColor = tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color; 27 | float gray = (texColor.r + texColor.g + texColor.b) / 3.0; 28 | return float4(gray, gray, gray, texColor.a); 29 | } 30 | 31 | technique SpriteDrawing 32 | { 33 | pass P0 34 | { 35 | PixelShader = compile PS_SHADERMODEL MainPS(); 36 | } 37 | }; -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Game1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | 5 | 6 | namespace Iguina.Demo.MonoGame 7 | { 8 | public class Game1 : Game 9 | { 10 | private GraphicsDeviceManager _graphics = null!; 11 | private SpriteBatch _spriteBatch = null!; 12 | MonoGameRenderer _renderer = null!; 13 | MonoGameInput _input = null!; 14 | IguinaDemoStarter _demo = null!; 15 | 16 | public Game1() 17 | { 18 | _graphics = new GraphicsDeviceManager(this); 19 | Content.RootDirectory = "Content"; 20 | IsMouseVisible = true; 21 | Window.Title = "Iguina Demo - MonoGame"; 22 | } 23 | 24 | protected override void Initialize() 25 | { 26 | base.Initialize(); 27 | } 28 | 29 | protected override void LoadContent() 30 | { 31 | _spriteBatch = new SpriteBatch(GraphicsDevice); 32 | 33 | // start demo project and provide our renderer and input provider. 34 | var uiThemeFolder = "../../../../Iguina.Demo/Assets/DefaultTheme"; 35 | 36 | // create demo 37 | _demo = new IguinaDemoStarter(); 38 | _renderer = new MonoGameRenderer(Content, GraphicsDevice, _spriteBatch, uiThemeFolder); 39 | _input = new MonoGameInput(); 40 | _demo.Start(_renderer, _input, uiThemeFolder); 41 | 42 | // set maximized 43 | { 44 | int _ScreenWidth = GraphicsDevice.Adapter.CurrentDisplayMode.Width; 45 | int _ScreenHeight = GraphicsDevice.Adapter.CurrentDisplayMode.Height; 46 | _graphics.PreferredBackBufferWidth = (int)_ScreenWidth; 47 | _graphics.PreferredBackBufferHeight = (int)_ScreenHeight; 48 | IsMouseVisible = false; 49 | Window.AllowUserResizing = true; 50 | _graphics.IsFullScreen = false; 51 | _graphics.ApplyChanges(); 52 | Window.AllowUserResizing = true; 53 | Window.IsBorderless = false; 54 | Window.Position = new Point(0, 0); 55 | } 56 | } 57 | 58 | protected override void Update(GameTime gameTime) 59 | { 60 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) 61 | Exit(); 62 | 63 | _input.StartFrame(gameTime); 64 | _demo.Update((float)gameTime.ElapsedGameTime.TotalSeconds); 65 | _input.EndFrame(); 66 | 67 | base.Update(gameTime); 68 | } 69 | 70 | protected override void Draw(GameTime gameTime) 71 | { 72 | GraphicsDevice.Clear(Color.CornflowerBlue); 73 | 74 | _renderer.StartFrame(); 75 | _demo.Draw(); 76 | _renderer.EndFrame(); 77 | 78 | base.Draw(gameTime); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Icon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/Iguina.Demo.MonoGame/Icon.bmp -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/Iguina.Demo.MonoGame/Icon.ico -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Iguina.Demo.MonoGame.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net8.0 5 | Major 6 | false 7 | false 8 | 9 | 10 | app.manifest 11 | Icon.ico 12 | enable 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/MonoGameInput.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Drivers; 3 | using Microsoft.Xna.Framework.Input; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Iguina.Demo.MonoGame 9 | { 10 | /// 11 | /// Provide input for GUI. 12 | /// 13 | internal class MonoGameInput : Drivers.IInputProvider 14 | { 15 | int _lastWheelValue; 16 | 17 | private List _textInput = new(); 18 | private const double InitialDelay = 0.45; // Initial delay before repeating input 19 | private const double RepeatRate = 0.045; // Rate of repeated input 20 | private double[] _charsDelay = new double[255]; 21 | Keys[] _lastPressedKeys = new Keys[0]; 22 | 23 | public void StartFrame(Microsoft.Xna.Framework.GameTime gameTime) 24 | { 25 | var keyboardState = Keyboard.GetState(); 26 | var keysPressed = keyboardState.GetPressedKeys(); 27 | 28 | // get keyboard text input 29 | { 30 | HashSet _lastKeys = new(_lastPressedKeys); 31 | _textInput.Clear(); 32 | var currSeconds = gameTime.TotalGameTime.TotalSeconds; 33 | 34 | foreach (var key in keysPressed) 35 | { 36 | char keyChar = ConvertKeyToChar(key, keyboardState); 37 | int keyCharForDelay = (int)keyChar.ToString().ToLower()[0]; 38 | if (keyChar != '\0') 39 | { 40 | if ((currSeconds > _charsDelay[keyCharForDelay]) || (!_lastPressedKeys.Contains(key))) 41 | { 42 | _textInput.Add(keyChar); 43 | _charsDelay[keyCharForDelay] = currSeconds + (_lastKeys.Contains(key) ? RepeatRate : InitialDelay); 44 | } 45 | } 46 | } 47 | _lastPressedKeys = keysPressed; 48 | } 49 | } 50 | 51 | public void EndFrame() 52 | { 53 | var mouse = Mouse.GetState(); 54 | _lastWheelValue = mouse.ScrollWheelValue; 55 | } 56 | 57 | public Point GetMousePosition() 58 | { 59 | var mouse =Mouse.GetState(); 60 | return new Point(mouse.X, mouse.Y); 61 | } 62 | 63 | public bool IsMouseButtonDown(MouseButton btn) 64 | { 65 | var mouse =Mouse.GetState(); 66 | switch (btn) 67 | { 68 | case MouseButton.Left: return mouse.LeftButton == ButtonState.Pressed; 69 | case MouseButton.Right: return mouse.RightButton == ButtonState.Pressed; 70 | case MouseButton.Wheel: return mouse.MiddleButton == ButtonState.Pressed; 71 | } 72 | return false; 73 | } 74 | 75 | public int GetMouseWheelChange() 76 | { 77 | var mouse =Mouse.GetState(); 78 | return Math.Sign(mouse.ScrollWheelValue - _lastWheelValue); 79 | } 80 | 81 | 82 | public int[] GetTextInput() 83 | { 84 | return _textInput.ToArray(); 85 | } 86 | 87 | 88 | private char ConvertKeyToChar(Keys key, KeyboardState state) 89 | { 90 | bool shift = state.IsKeyDown(Keys.LeftShift) || state.IsKeyDown(Keys.RightShift); 91 | bool capsLock = state.CapsLock; 92 | 93 | switch (key) 94 | { 95 | case Keys.A: return shift ^ capsLock ? 'A' : 'a'; 96 | case Keys.B: return shift ^ capsLock ? 'B' : 'b'; 97 | case Keys.C: return shift ^ capsLock ? 'C' : 'c'; 98 | case Keys.D: return shift ^ capsLock ? 'D' : 'd'; 99 | case Keys.E: return shift ^ capsLock ? 'E' : 'e'; 100 | case Keys.F: return shift ^ capsLock ? 'F' : 'f'; 101 | case Keys.G: return shift ^ capsLock ? 'G' : 'g'; 102 | case Keys.H: return shift ^ capsLock ? 'H' : 'h'; 103 | case Keys.I: return shift ^ capsLock ? 'I' : 'i'; 104 | case Keys.J: return shift ^ capsLock ? 'J' : 'j'; 105 | case Keys.K: return shift ^ capsLock ? 'K' : 'k'; 106 | case Keys.L: return shift ^ capsLock ? 'L' : 'l'; 107 | case Keys.M: return shift ^ capsLock ? 'M' : 'm'; 108 | case Keys.N: return shift ^ capsLock ? 'N' : 'n'; 109 | case Keys.O: return shift ^ capsLock ? 'O' : 'o'; 110 | case Keys.P: return shift ^ capsLock ? 'P' : 'p'; 111 | case Keys.Q: return shift ^ capsLock ? 'Q' : 'q'; 112 | case Keys.R: return shift ^ capsLock ? 'R' : 'r'; 113 | case Keys.S: return shift ^ capsLock ? 'S' : 's'; 114 | case Keys.T: return shift ^ capsLock ? 'T' : 't'; 115 | case Keys.U: return shift ^ capsLock ? 'U' : 'u'; 116 | case Keys.V: return shift ^ capsLock ? 'V' : 'v'; 117 | case Keys.W: return shift ^ capsLock ? 'W' : 'w'; 118 | case Keys.X: return shift ^ capsLock ? 'X' : 'x'; 119 | case Keys.Y: return shift ^ capsLock ? 'Y' : 'y'; 120 | case Keys.Z: return shift ^ capsLock ? 'Z' : 'z'; 121 | case Keys.D0: return shift ? ')' : '0'; 122 | case Keys.D1: return shift ? '!' : '1'; 123 | case Keys.D2: return shift ? '@' : '2'; 124 | case Keys.D3: return shift ? '#' : '3'; 125 | case Keys.D4: return shift ? '$' : '4'; 126 | case Keys.D5: return shift ? '%' : '5'; 127 | case Keys.D6: return shift ? '^' : '6'; 128 | case Keys.D7: return shift ? '&' : '7'; 129 | case Keys.D8: return shift ? '*' : '8'; 130 | case Keys.D9: return shift ? '(' : '9'; 131 | case Keys.Space: return ' '; 132 | case Keys.OemPeriod: return shift ? '>' : '.'; 133 | case Keys.OemComma: return shift ? '<' : ','; 134 | case Keys.OemQuestion: return shift ? '?' : '/'; 135 | case Keys.OemSemicolon: return shift ? ':' : ';'; 136 | case Keys.OemQuotes: return shift ? '"' : '\''; 137 | case Keys.OemBackslash: return shift ? '|' : '\\'; 138 | case Keys.OemOpenBrackets: return shift ? '{' : '['; 139 | case Keys.OemCloseBrackets: return shift ? '}' : ']'; 140 | case Keys.OemMinus: return shift ? '_' : '-'; 141 | case Keys.OemPlus: return shift ? '+' : '='; 142 | default: return '\0'; 143 | } 144 | } 145 | 146 | public TextInputCommands[] GetTextInputCommands() 147 | { 148 | List ret = new(); 149 | var keyboard = Keyboard.GetState(); 150 | var ctrlDown = keyboard.IsKeyDown(Keys.LeftControl) || keyboard.IsKeyDown(Keys.RightControl); 151 | long millisecondsSinceEpoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 152 | { 153 | foreach (var value in Enum.GetValues(typeof(TextInputCommands))) 154 | { 155 | var key = _inputTextCommandToKeyboardKey[(int)value]; 156 | long msPassed = millisecondsSinceEpoch - _timeToAllowNextInputCommand[(int)value]; 157 | if (keyboard.IsKeyDown(key)) 158 | { 159 | if (msPassed > 0) 160 | { 161 | _timeToAllowNextInputCommand[(int)value] = (millisecondsSinceEpoch + (msPassed >= 250 ? 450 : 45)); 162 | var command = (TextInputCommands)value; 163 | if ((command == TextInputCommands.MoveCaretEnd) && !ctrlDown) { continue; } 164 | if ((command == TextInputCommands.MoveCaretEndOfLine) && ctrlDown) { continue; } 165 | if ((command == TextInputCommands.MoveCaretStart) && !ctrlDown) { continue; } 166 | if ((command == TextInputCommands.MoveCaretStartOfLine) && ctrlDown) { continue; } 167 | ret.Add(command); 168 | } 169 | } 170 | else 171 | { 172 | _timeToAllowNextInputCommand[(int)value] = 0; 173 | } 174 | } 175 | } 176 | return ret.ToArray(); 177 | } 178 | 179 | // to add rate delay and ray limit to input commands 180 | long[] _timeToAllowNextInputCommand = new long[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 181 | 182 | // convert text input command to keyboard key 183 | /* 184 | * enum values: 185 | MoveCaretLeft, 186 | MoveCaretRight, 187 | MoveCaretUp, 188 | MoveCaretDown, 189 | Backspace, 190 | Delete, 191 | End, 192 | Start, 193 | EndOfLine, 194 | StartOfLine 195 | */ 196 | static Keys[] _inputTextCommandToKeyboardKey = new Keys[] 197 | { 198 | Keys.Left, 199 | Keys.Right, 200 | Keys.Up, 201 | Keys.Down, 202 | Keys.Back, 203 | Keys.Delete, 204 | Keys.Enter, 205 | Keys.End, 206 | Keys.Home, 207 | Keys.End, 208 | Keys.Home 209 | }; 210 | 211 | public KeyboardInteractions? GetKeyboardInteraction() 212 | { 213 | var keyboardState = Keyboard.GetState(); 214 | 215 | if (keyboardState.IsKeyDown(Keys.Left)) 216 | { 217 | return KeyboardInteractions.MoveLeft; 218 | } 219 | if (keyboardState.IsKeyDown(Keys.Right)) 220 | { 221 | return KeyboardInteractions.MoveRight; 222 | } 223 | if (keyboardState.IsKeyDown(Keys.Up)) 224 | { 225 | return KeyboardInteractions.MoveUp; 226 | } 227 | if (keyboardState.IsKeyDown(Keys.Down)) 228 | { 229 | return KeyboardInteractions.MoveDown; 230 | } 231 | if (keyboardState.IsKeyDown(Keys.Space) || keyboardState.IsKeyDown(Keys.Enter)) 232 | { 233 | return KeyboardInteractions.Select; 234 | } 235 | return null; 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | using var game = new Iguina.Demo.MonoGame.Game1(); 3 | game.Run(); 4 | -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/ReadmeExample.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Entities; 3 | using Microsoft.Xna.Framework; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using Microsoft.Xna.Framework.Input; 6 | using System.IO; 7 | 8 | namespace Iguina.Demo.MonoGame 9 | { 10 | /// 11 | /// Basic monogame example. 12 | /// 13 | public class IguinaMonoGameExample : Game 14 | { 15 | private GraphicsDeviceManager _graphics = null!; 16 | private SpriteBatch _spriteBatch = null!; 17 | UISystem _uiSystem = null!; 18 | 19 | public IguinaMonoGameExample() 20 | { 21 | _graphics = new GraphicsDeviceManager(this); 22 | Content.RootDirectory = "Content"; 23 | IsMouseVisible = true; 24 | Window.Title = "Iguina Demo - MonoGame"; 25 | } 26 | 27 | protected override void Initialize() 28 | { 29 | base.Initialize(); 30 | } 31 | 32 | protected override void LoadContent() 33 | { 34 | _spriteBatch = new SpriteBatch(GraphicsDevice); 35 | 36 | // start demo project and provide our renderer and input provider. 37 | var uiThemeFolder = "../../../../Iguina.Demo/Assets/DefaultTheme"; 38 | 39 | // create ui system 40 | var renderer = new MonoGameRenderer(Content, GraphicsDevice, _spriteBatch, uiThemeFolder); 41 | var input = new MonoGameInput(); 42 | _uiSystem = new UISystem(Path.Combine(uiThemeFolder, "system_style.json"), renderer, input); 43 | 44 | // create panel with hello message 45 | { 46 | var panel = new Panel(_uiSystem); 47 | panel.Anchor = Defs.Anchor.Center; 48 | panel.Size.SetPixels(400, 400); 49 | _uiSystem.Root.AddChild(panel); 50 | 51 | var paragraph = new Paragraph(_uiSystem); 52 | paragraph.Text = "Hello World!"; 53 | panel.AddChild(paragraph); 54 | } 55 | } 56 | 57 | protected override void Update(GameTime gameTime) 58 | { 59 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) 60 | Exit(); 61 | 62 | // update input and ui system 63 | var input = (_uiSystem.Input as MonoGameInput)!; 64 | input.StartFrame(gameTime); 65 | _uiSystem.Update((float)gameTime.ElapsedGameTime.TotalSeconds); 66 | input.EndFrame(); 67 | 68 | base.Update(gameTime); 69 | } 70 | 71 | protected override void Draw(GameTime gameTime) 72 | { 73 | GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue); 74 | 75 | // render ui 76 | var renderer = (_uiSystem.Renderer as MonoGameRenderer)!; 77 | renderer.StartFrame(); 78 | _uiSystem.Draw(); 79 | renderer.EndFrame(); 80 | 81 | base.Draw(gameTime); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Iguina.Demo.MonoGame/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true/pm 39 | permonitorv2,permonitor 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Iguina.Demo.RayLib/Iguina.Demo.RayLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Iguina.Demo.RayLib/Program.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Demo; 2 | using Iguina.Demo.RayLib; 3 | using Raylib_cs; 4 | using static Raylib_cs.Raylib; 5 | 6 | // uncomment this to play the example from the readme.md file 7 | //ReadmeDemo.Start(); 8 | 9 | // get screen resolution for demo size 10 | int screenWidth = Raylib_cs.Raylib.GetScreenWidth(); 11 | int screenHeight = Raylib_cs.Raylib.GetScreenHeight(); 12 | 13 | // init window 14 | Raylib.SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.ResizableWindow); 15 | InitWindow(screenWidth, screenHeight, "Iguina Demo - RayLib"); 16 | //SetWindowState(ConfigFlags.BorderlessWindowMode); 17 | MaximizeWindow(); 18 | SetTargetFPS(0); 19 | 20 | // start demo project and provide our renderer and input provider. 21 | var uiThemeFolder = "../../../../Iguina.Demo/Assets/DefaultTheme"; 22 | var demo = new IguinaDemoStarter(); 23 | var renderer = new RayLibRenderer(uiThemeFolder); 24 | var input = new RayLibInput(); 25 | demo.Start(renderer, input, uiThemeFolder); 26 | 27 | // Main game loop 28 | while (!WindowShouldClose()) 29 | { 30 | // begin drawing 31 | BeginDrawing(); 32 | ClearBackground(Color.DarkBlue); 33 | 34 | // show / hide cursor 35 | if (IsWindowFocused()) { HideCursor(); } 36 | else { ShowCursor(); } 37 | 38 | // update and draw ui 39 | BeginMode2D(new Camera2D() { Zoom = 1f }); 40 | renderer.StartFrame(); 41 | demo.Update(GetFrameTime()); 42 | demo.Draw(); 43 | renderer.EndFrame(); 44 | EndMode2D(); 45 | 46 | // end drawing 47 | EndDrawing(); 48 | } 49 | 50 | CloseWindow(); 51 | Environment.Exit(0); -------------------------------------------------------------------------------- /Iguina.Demo.RayLib/RayLibInput.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Drivers; 3 | 4 | 5 | namespace Iguina.Demo.RayLib 6 | { 7 | 8 | /// 9 | /// Provide input for the GUI system. 10 | /// 11 | public class RayLibInput : IInputProvider 12 | { 13 | public Point GetMousePosition() 14 | { 15 | var cp = Raylib_cs.Raylib.GetMousePosition(); 16 | return new Point((int)cp.X, (int)cp.Y); 17 | } 18 | 19 | public bool IsMouseButtonDown(MouseButton btn) 20 | { 21 | switch (btn) 22 | { 23 | case MouseButton.Left: 24 | return Raylib_cs.Raylib.IsMouseButtonDown(Raylib_cs.MouseButton.Left); 25 | 26 | case MouseButton.Right: 27 | return Raylib_cs.Raylib.IsMouseButtonDown(Raylib_cs.MouseButton.Right); 28 | 29 | case MouseButton.Wheel: 30 | return Raylib_cs.Raylib.IsMouseButtonDown(Raylib_cs.MouseButton.Middle); 31 | } 32 | return false; 33 | } 34 | 35 | public int GetMouseWheelChange() 36 | { 37 | float val = Raylib_cs.Raylib.GetMouseWheelMove(); 38 | return MathF.Sign(val); 39 | } 40 | 41 | public int[] GetTextInput() 42 | { 43 | List ret = new(); 44 | while (true) 45 | { 46 | var curr = Raylib_cs.Raylib.GetCharPressed(); 47 | if (curr != 0) { ret.Add(curr); } 48 | else { break; } 49 | } 50 | return ret.ToArray(); 51 | } 52 | 53 | public TextInputCommands[] GetTextInputCommands() 54 | { 55 | var ctrlDown = Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.LeftControl) || Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Right); 56 | 57 | List ret = new(); 58 | long millisecondsSinceEpoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); 59 | { 60 | foreach (var value in Enum.GetValues(typeof(TextInputCommands))) 61 | { 62 | var key = _inputTextCommandToKeyboardKey[(int)value]; 63 | long msPassed = millisecondsSinceEpoch - _timeToAllowNextInputCommand[(int)value]; 64 | if (Raylib_cs.Raylib.IsKeyDown(key)) 65 | { 66 | if (msPassed > 0) 67 | { 68 | _timeToAllowNextInputCommand[(int)value] = (millisecondsSinceEpoch + (msPassed >= 250 ? 450 : 45)); 69 | var command = (TextInputCommands)value; 70 | if ((command == TextInputCommands.MoveCaretEnd) && !ctrlDown) { continue; } 71 | if ((command == TextInputCommands.MoveCaretEndOfLine) && ctrlDown) { continue; } 72 | if ((command == TextInputCommands.MoveCaretStart) && !ctrlDown) { continue; } 73 | if ((command == TextInputCommands.MoveCaretStartOfLine) && ctrlDown) { continue; } 74 | ret.Add(command); 75 | } 76 | } 77 | else 78 | { 79 | _timeToAllowNextInputCommand[(int)value] = 0; 80 | } 81 | } 82 | } 83 | return ret.ToArray(); 84 | } 85 | 86 | // to add rate delay and ray limit to input commands 87 | long[] _timeToAllowNextInputCommand = new long[] { 0,0,0,0,0,0,0,0,0,0,0 }; 88 | 89 | // convert text input command to keyboard key 90 | /* 91 | * enum values: 92 | MoveCaretLeft, 93 | MoveCaretRight, 94 | MoveCaretUp, 95 | MoveCaretDown, 96 | Backspace, 97 | Delete, 98 | End, 99 | Start, 100 | EndOfLine, 101 | StartOfLine 102 | */ 103 | static Raylib_cs.KeyboardKey[] _inputTextCommandToKeyboardKey = new Raylib_cs.KeyboardKey[] 104 | { 105 | Raylib_cs.KeyboardKey.Left, 106 | Raylib_cs.KeyboardKey.Right, 107 | Raylib_cs.KeyboardKey.Up, 108 | Raylib_cs.KeyboardKey.Down, 109 | Raylib_cs.KeyboardKey.Backspace, 110 | Raylib_cs.KeyboardKey.Delete, 111 | Raylib_cs.KeyboardKey.Enter, 112 | Raylib_cs.KeyboardKey.End, 113 | Raylib_cs.KeyboardKey.Home, 114 | Raylib_cs.KeyboardKey.End, 115 | Raylib_cs.KeyboardKey.Home 116 | }; 117 | 118 | public KeyboardInteractions? GetKeyboardInteraction() 119 | { 120 | if (Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Left)) 121 | { 122 | return KeyboardInteractions.MoveLeft; 123 | } 124 | if (Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Right)) 125 | { 126 | return KeyboardInteractions.MoveRight; 127 | } 128 | if (Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Up)) 129 | { 130 | return KeyboardInteractions.MoveUp; 131 | } 132 | if (Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Down)) 133 | { 134 | return KeyboardInteractions.MoveDown; 135 | } 136 | if (Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Space) || Raylib_cs.Raylib.IsKeyDown(Raylib_cs.KeyboardKey.Enter)) 137 | { 138 | return KeyboardInteractions.Select; 139 | } 140 | return null; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Iguina.Demo.RayLib/RayLibRenderer.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Drivers; 3 | using System.Numerics; 4 | 5 | 6 | namespace Iguina.Demo.RayLib 7 | { 8 | /// 9 | /// Provide rendering for the GUI system. 10 | /// 11 | public class RayLibRenderer : IRenderer 12 | { 13 | // assets path 14 | string _assetsPath; 15 | 16 | /// 17 | /// Create the ray renderer. 18 | /// 19 | /// Root directory to load assets from. Check out the demo project for details. 20 | public RayLibRenderer(string assetsPath) 21 | { 22 | // store textures path 23 | _assetsPath = assetsPath; 24 | 25 | // create black and white effect for disabled entities 26 | { 27 | string grayscaleShader = @" 28 | #version 330 29 | in vec2 fragTexCoord; 30 | in vec4 fragColor; 31 | 32 | uniform sampler2D texture0; 33 | uniform vec4 colDiffuse; 34 | 35 | out vec4 finalColor; 36 | 37 | void main() 38 | { 39 | vec4 texelColor = texture(texture0, fragTexCoord) * colDiffuse * fragColor; 40 | float gray = (texelColor.r + texelColor.g + texelColor.b) / 3; 41 | finalColor = vec4(vec3(gray), texelColor.a); 42 | } 43 | "; 44 | _disabledShader = Raylib_cs.Raylib.LoadShaderFromMemory(null, grayscaleShader); 45 | } 46 | 47 | // get default font 48 | _defaultFont = Raylib_cs.Raylib.GetFontDefault(); 49 | } 50 | 51 | // shader to use for disabled effect 52 | Raylib_cs.Shader _disabledShader; 53 | 54 | // last effect id 55 | string? _lastEffect = null!; 56 | 57 | /// 58 | /// Called at the beginning of every frame. 59 | /// 60 | public void StartFrame() 61 | { 62 | _lastEffect = null; 63 | } 64 | 65 | /// 66 | /// Called at the end of every frame. 67 | /// 68 | public void EndFrame() 69 | { 70 | if (_lastEffect != null) 71 | { 72 | Raylib_cs.Raylib.EndShaderMode(); 73 | } 74 | } 75 | 76 | /// 77 | /// Set which effect to use. 78 | /// 79 | void SetEffect(string? effect) 80 | { 81 | // effect changed? 82 | if (effect != _lastEffect) 83 | { 84 | // cancel previous shader 85 | if (_lastEffect != null) 86 | { 87 | Raylib_cs.Raylib.EndShaderMode(); 88 | } 89 | 90 | // no effect 91 | if (effect == null) 92 | { 93 | } 94 | // set disabled effect 95 | else if (effect == "disabled") 96 | { 97 | Raylib_cs.Raylib.BeginShaderMode(_disabledShader); 98 | } 99 | // undefined effect! 100 | else 101 | { 102 | throw new Exception("Unknown effect identifier: " + effect); 103 | } 104 | 105 | // set last effect 106 | _lastEffect = effect; 107 | } 108 | } 109 | 110 | /// 111 | /// Get texture instance from texture id. 112 | /// 113 | Raylib_cs.Texture2D GetTexture(string textureId) 114 | { 115 | if (_textures.TryGetValue(textureId, out Raylib_cs.Texture2D tex)) 116 | { 117 | return tex; 118 | } 119 | _textures[textureId] = Raylib_cs.Raylib.LoadTexture(Path.Combine(_assetsPath, textureId)); 120 | return _textures[textureId]; 121 | } 122 | 123 | // cached textures 124 | Dictionary _textures = new(); 125 | 126 | /// 127 | /// Get font from identifier. 128 | /// 129 | Raylib_cs.Font GetFont(string? fontId) 130 | { 131 | if (fontId == null) 132 | { 133 | return _defaultFont; 134 | } 135 | 136 | if (_fonts.TryGetValue(fontId, out Raylib_cs.Font font)) 137 | { 138 | return font; 139 | } 140 | _fonts[fontId] = Raylib_cs.Raylib.LoadFont(Path.Combine(_assetsPath, fontId)); 141 | return _fonts[fontId]; 142 | } 143 | 144 | // cached fonts 145 | Dictionary _fonts = new(); 146 | Raylib_cs.Font _defaultFont; 147 | 148 | /// 149 | public Rectangle GetScreenBounds() 150 | { 151 | return new Rectangle(0, 0, Raylib_cs.Raylib.GetRenderWidth(), Raylib_cs.Raylib.GetRenderHeight()); 152 | } 153 | 154 | /// 155 | [Obsolete("Note: currently we render outline in a primitive way. To improve performance and remove some visual artifact during transitions, its best to implement a shader that draw text with outline properly.")] 156 | public void DrawText(string? effectIdentifier, string text, string? fontId, int fontSize, Point position, Color fillColor, Color outlineColor, int outlineWidth, float spacing) 157 | { 158 | // set text effect 159 | SetEffect(effectIdentifier); 160 | 161 | // get font 162 | var font = GetFont(fontId); 163 | 164 | // draw outline 165 | if ((outlineColor.A > 0) && (outlineWidth > 0)) 166 | { 167 | // because we draw outline in a primitive way, we want it to fade a lot faster than fill color 168 | if (outlineColor.A < 255) 169 | { 170 | float alphaFactor = (float)(outlineColor.A / 255f); 171 | outlineColor.A = (byte)((float)fillColor.A * Math.Pow(alphaFactor, 7)); 172 | } 173 | 174 | // draw outline 175 | var outline = new Raylib_cs.Color(outlineColor.R, outlineColor.G, outlineColor.B, outlineColor.A); 176 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X - outlineWidth, position.Y), fontSize, spacing, outline); 177 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X, position.Y - outlineWidth), fontSize, spacing, outline); 178 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X + outlineWidth, position.Y), fontSize, spacing, outline); 179 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X, position.Y + outlineWidth), fontSize, spacing, outline); 180 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X - outlineWidth, position.Y - outlineWidth), fontSize, spacing, outline); 181 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X - outlineWidth, position.Y + outlineWidth), fontSize, spacing, outline); 182 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X + outlineWidth, position.Y - outlineWidth), fontSize, spacing, outline); 183 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X + outlineWidth, position.Y + outlineWidth), fontSize, spacing, outline); 184 | } 185 | 186 | // draw text 187 | var fill = new Raylib_cs.Color(fillColor.R, fillColor.G, fillColor.B, fillColor.A); 188 | Raylib_cs.Raylib.DrawTextEx(font, text, new System.Numerics.Vector2(position.X, position.Y), fontSize, spacing, fill); 189 | } 190 | 191 | /// 192 | public int GetTextLineHeight(string? fontId, int fontSize) 193 | { 194 | var font = GetFont(fontId); 195 | float scale = fontSize / (float)font.BaseSize; 196 | return (int)(font.BaseSize * scale); 197 | } 198 | 199 | /// 200 | public Point MeasureText(string text, string? fontId, int fontSize, float spacing) 201 | { 202 | var font = GetFont(fontId); 203 | var ret = Raylib_cs.Raylib.MeasureTextEx(font, text, fontSize, spacing); 204 | return new Point((int)ret.X, (int)ret.Y); 205 | } 206 | 207 | /// 208 | public void DrawTexture(string? effectIdentifier, string textureId, Rectangle destRect, Rectangle sourceRect, Color color) 209 | { 210 | // set active effect 211 | SetEffect(effectIdentifier); 212 | 213 | // get texture 214 | var texture = GetTexture(textureId); 215 | 216 | // draw texture 217 | Raylib_cs.Raylib.DrawTexturePro(texture, 218 | new Raylib_cs.Rectangle(sourceRect.X, sourceRect.Y, sourceRect.Width, sourceRect.Height), 219 | new Raylib_cs.Rectangle(destRect.X, destRect.Y, destRect.Width, destRect.Height), 220 | new System.Numerics.Vector2(0f, 0f), 221 | 0f, 222 | new Raylib_cs.Color(color.R, color.G, color.B, color.A)); 223 | } 224 | 225 | // currently set scissor region 226 | Rectangle? _currentScissorRegion; 227 | 228 | /// 229 | public void SetScissorRegion(Rectangle region) 230 | { 231 | _currentScissorRegion = region; 232 | Raylib_cs.Raylib.BeginScissorMode(region.X, region.Y, region.Width, region.Height); 233 | } 234 | 235 | /// 236 | public void DrawRectangle(Rectangle rectangle, Color color) 237 | { 238 | SetEffect(null); 239 | Raylib_cs.Raylib.DrawRectangle(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height, new Raylib_cs.Color(color.R, color.G, color.B, color.A)); 240 | } 241 | 242 | /// 243 | public void ClearScissorRegion() 244 | { 245 | _currentScissorRegion = null; 246 | Raylib_cs.Raylib.EndScissorMode(); 247 | } 248 | 249 | /// 250 | public Rectangle? GetScissorRegion() 251 | { 252 | return _currentScissorRegion; 253 | } 254 | 255 | /// 256 | /// Get image from texture. 257 | /// 258 | Raylib_cs.Image GetImageFromTexture(string textureId) 259 | { 260 | Raylib_cs.Image image; 261 | var texture = GetTexture(textureId); 262 | if (!_cachedImageData.TryGetValue(textureId, out image)) 263 | { 264 | image = Raylib_cs.Raylib.LoadImageFromTexture(texture); 265 | _cachedImageData[textureId] = image; 266 | } 267 | return image; 268 | } 269 | Dictionary _cachedImageData = new(); 270 | 271 | /// 272 | public Color GetPixelFromTexture(string textureId, Point sourcePosition) 273 | { 274 | var image = GetImageFromTexture(textureId); 275 | if (sourcePosition.X < 0) sourcePosition.X = 0; 276 | if (sourcePosition.Y < 0) sourcePosition.Y = 0; 277 | if (sourcePosition.X >= image.Width) sourcePosition.X = image.Width - 1; 278 | if (sourcePosition.Y >= image.Height) sourcePosition.Y = image.Height - 1; 279 | var pixelColor = Raylib_cs.Raylib.GetImageColor(image, sourcePosition.X, sourcePosition.Y); 280 | return new Color(pixelColor.R, pixelColor.G, pixelColor.B, pixelColor.A); 281 | } 282 | 283 | /// 284 | public Point? FindPixelOffsetInTexture(string textureId, Rectangle sourceRect, Color color, bool returnNearestColor) 285 | { 286 | Point? ret = null; 287 | float nearestDistance = 255f * 255f * 255f * 255f; 288 | var image = GetImageFromTexture(textureId); 289 | for (int x = 0; x < sourceRect.Width; x++) 290 | { 291 | for (int y = 0; y < sourceRect.Height; y++) 292 | { 293 | var pixelColor = Raylib_cs.Raylib.GetImageColor(image, x + sourceRect.Left, y + sourceRect.Top); 294 | if ((pixelColor.R == color.R) && (pixelColor.G == color.G) && (pixelColor.B == color.B) && (pixelColor.A == color.A)) 295 | { 296 | return new Point(x, y); 297 | } 298 | else if (returnNearestColor) 299 | { 300 | float distance = Vector4.DistanceSquared(new Vector4(pixelColor.R, pixelColor.G, pixelColor.B, pixelColor.A), new Vector4(color.R, color.G, color.B, color.A)); 301 | if (distance < nearestDistance) 302 | { 303 | nearestDistance = distance; 304 | ret = new Point(x, y); 305 | } 306 | } 307 | } 308 | } 309 | return ret; 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /Iguina.Demo.RayLib/ReadmeExample.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Demo.RayLib; 3 | using Iguina.Entities; 4 | using Raylib_cs; 5 | using static Raylib_cs.Raylib; 6 | 7 | static class ReadmeDemo 8 | { 9 | public static void Start() 10 | { 11 | // get screen resolution for demo size 12 | int screenWidth = 800; 13 | int screenHeight = 600; 14 | 15 | // init window 16 | Raylib.SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.ResizableWindow); 17 | InitWindow(screenWidth, screenHeight, "Iguina.Demo.RayLib"); 18 | 19 | // start demo project and provide our renderer and input provider. 20 | var uiThemeFolder = "../../../../Iguina.Demo/Assets/DefaultTheme"; 21 | var renderer = new RayLibRenderer(uiThemeFolder); 22 | var input = new RayLibInput(); 23 | var uiSystem = new Iguina.UISystem(Path.Combine(uiThemeFolder, "system_style.json"), renderer, input); 24 | 25 | // create panel with hello message 26 | { 27 | var panel = new Panel(uiSystem); 28 | panel.Anchor = Anchor.Center; 29 | panel.Size.SetPixels(400, 400); 30 | uiSystem.Root.AddChild(panel); 31 | 32 | var paragraph = new Paragraph(uiSystem); 33 | paragraph.Text = "Hello World!"; 34 | panel.AddChild(paragraph); 35 | } 36 | 37 | // Main game loop 38 | while (!WindowShouldClose()) 39 | { 40 | // begin drawing 41 | BeginDrawing(); 42 | ClearBackground(Raylib_cs.Color.DarkBlue); 43 | 44 | // update and draw ui 45 | BeginMode2D(new Camera2D() { Zoom = 1f }); 46 | renderer.StartFrame(); 47 | uiSystem.Update(GetFrameTime()); 48 | uiSystem.Draw(); 49 | renderer.EndFrame(); 50 | EndMode2D(); 51 | 52 | // end drawing 53 | EndDrawing(); 54 | } 55 | 56 | CloseWindow(); 57 | Environment.Exit(0); 58 | } 59 | } -------------------------------------------------------------------------------- /Iguina.Demo.RayLib/readme.md: -------------------------------------------------------------------------------- 1 | # Iguina.Demo.RayLib 2 | 3 | RayLib demo project. 4 | This project merely implement the drivers layer with RayLib (renderer + input provider) and use the `Iguina.Demo` project to create an example UI system. 5 | 6 | For full documentation, check out the readme file at the repository root. -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/button.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "FillTextureFramed": { 5 | 6 | "InternalSourceRect": {"X": 116, "Y": 4, "Width": 72, "Height": 24}, 7 | "ExternalSourceRect": {"X": 112, "Y": 0, "Width": 80, "Height": 32} 8 | }, 9 | "TextAlignment": "Center", 10 | "Padding": {"Left": 8, "Right": 8, "Top": 8, "Bottom": 8} 11 | }, 12 | "Targeted": { 13 | "FillTextureFramed": { 14 | 15 | "InternalSourceRect": {"X": 116, "Y": 36, "Width": 72, "Height": 24}, 16 | "ExternalSourceRect": {"X": 112, "Y": 32, "Width": 80, "Height": 32} 17 | } 18 | }, 19 | "Interacted": { 20 | "FillTextureFramed": { 21 | 22 | "InternalSourceRect": {"X": 116, "Y": 68, "Width": 72, "Height": 24}, 23 | "ExternalSourceRect": {"X": 112, "Y": 64, "Width": 80, "Height": 32} 24 | } 25 | }, 26 | "TargetedChecked": { 27 | }, 28 | "Checked": { 29 | }, 30 | "Disabled": { 31 | "EffectIdentifier": "disabled" 32 | }, 33 | "DefaultTextAnchor": "Center", 34 | "InterpolateStatesSpeed": 5 35 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/checkbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "Icon": { 5 | 6 | "SourceRect": {"X": 80, "Y": 96, "Width": 16, "Height": 16}, 7 | "CenterVertically": true 8 | }, 9 | "TextAlignment": "Left", 10 | "Padding": {"Left": 20, "Right": 0, "Top": 0, "Bottom": 0} 11 | }, 12 | "Targeted": { 13 | "Icon": { 14 | "SourceRect": {"X": 112, "Y": 96, "Width": 16, "Height": 16}, 15 | "CenterVertically": true 16 | } 17 | }, 18 | "TargetedChecked": { 19 | }, 20 | "Interacted": { 21 | "Icon": { 22 | 23 | "SourceRect": {"X": 96, "Y": 96, "Width": 16, "Height": 16}, 24 | "CenterVertically": true 25 | } 26 | }, 27 | "Checked": { 28 | }, 29 | "DefaultTextAnchor": "CenterLeft", 30 | "InterpolateStatesSpeed": 5 31 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/color_picker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureStretched": { 4 | 5 | "SourceRect": {"X": 128, "Y": 192, "Width": 128, "Height": 32} 6 | }, 7 | "Padding": {"Left": 0, "Right": 0, "Top": 0, "Bottom": 0}, 8 | "MarginBefore": {"X": 18, "Y": 18}, 9 | "MarginAfter": {"X": 18, "Y": 18} 10 | }, 11 | "Disabled": { 12 | "EffectIdentifier": "disabled" 13 | }, 14 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 15 | "DefaultHeight": {"Value": 96, "Units": "Pixels"}, 16 | "InterpolateStatesSpeed": 5, 17 | "InterpolateOffsetsSpeed": 10 18 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/color_picker_handle.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "Icon": { 4 | "SourceRect": {"X": 96, "Y": 192, "Width": 16, "Height": 16} 5 | 6 | } 7 | }, 8 | "Disabled": { 9 | "EffectIdentifier": "disabled" 10 | }, 11 | "DefaultWidth": {"Value": 32, "Units": "Pixels"}, 12 | "DefaultHeight": {"Value": 32, "Units": "Pixels"}, 13 | "InterpolateStatesSpeed": 5, 14 | "InterpolateOffsetsSpeed": 10 15 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/color_slider_horizontal.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureStretched": { 4 | "SourceRect": {"X": 128, "Y": 186, "Width": 128, "Height": 6}, 5 | "ExtraSize": {"Left": -10, "Right": -10, "Top": 0, "Bottom": 0} 6 | }, 7 | "Padding": {"Left": 10, "Right": 10, "Top": 0, "Bottom": 0}, 8 | "MarginBefore": {"X": 0, "Y": 18}, 9 | "MarginAfter": {"X": 0, "Y": 18} 10 | }, 11 | "Disabled": { 12 | "EffectIdentifier": "disabled" 13 | }, 14 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 15 | "DefaultHeight": {"Value": 18, "Units": "Pixels"}, 16 | "InterpolateStatesSpeed": 5, 17 | "InterpolateOffsetsSpeed": 10 18 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/color_slider_horizontal_handle.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "Icon": { 4 | "SourceRect": {"X": 123, "Y": 144, "Width": 10, "Height": 12}, 5 | "CenterVertically": true 6 | } 7 | }, 8 | "Targeted": { 9 | "Icon": { 10 | "SourceRect": {"X": 123, "Y": 156, "Width": 10, "Height": 12}, 11 | "CenterVertically": true 12 | } 13 | }, 14 | "Interacted": { 15 | "Icon": { 16 | "SourceRect": {"X": 123, "Y": 168, "Width": 10, "Height": 12}, 17 | "CenterVertically": true 18 | } 19 | }, 20 | "Disabled": { 21 | "EffectIdentifier": "disabled" 22 | }, 23 | "DefaultWidth": {"Value": 30, "Units": "Pixels"}, 24 | "DefaultHeight": {"Value": 36, "Units": "Pixels"}, 25 | "InterpolateStatesSpeed": 5, 26 | "InterpolateOffsetsSpeed": 10 27 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/dropdown_icon.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "Icon": { 4 | "SourceRect": {"X": 160, "Y": 96, "Width": 16, "Height": 16} 5 | } 6 | }, 7 | "Interacted": { 8 | "Icon": { 9 | "SourceRect": {"X": 176, "Y": 96, "Width": 16, "Height": 16} 10 | } 11 | }, 12 | "DefaultAnchor": "TopRight", 13 | "DefaultWidth": {"Value": 32, "Units": "Pixels"}, 14 | "DefaultHeight": {"Value": 32, "Units": "Pixels"} 15 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/horizontal_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | "InternalSourceRect": {"X": 3, "Y": 96, "Width": 74, "Height": 2}, 5 | "ExternalSourceRect": {"X": 0, "Y": 96, "Width": 80, "Height": 2} 6 | 7 | }, 8 | "Padding": {"Left": 8, "Right": 8, "Top": 0, "Bottom": 0}, 9 | "MarginBefore": {"X": 0, "Y": 5}, 10 | "MarginAfter": {"X": 0, "Y": 6} 11 | }, 12 | "Disabled": { 13 | "EffectIdentifier": "disabled" 14 | }, 15 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 16 | "DefaultHeight": {"Value": 8, "Units": "Pixels"} 17 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/label.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "TextFillColor": {"R": 220, "G": 220, "B": 220, "A": 255}, 5 | "TextScale": 0.85, 6 | "MarginAfter": {"X": 0, "Y": 2} 7 | } 8 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/list_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "TextAlignment": "Left", 5 | "DefaultTextAnchor": "CenterLeft", 6 | "ExtraSize": {"Left": 0, "Right": 0, "Top": 0, "Bottom": 10} 7 | }, 8 | "Targeted": { 9 | }, 10 | "Checked": { 11 | "FillTextureStretched": { 12 | 13 | "SourceRect": {"X": 0, "Y": 80, "Width": 16, "Height": 16}, 14 | "ExtraSize": {"Left": 20, "Right": 10, "Top": 10, "Bottom": 0} 15 | }, 16 | "TextFillColor": {"R": 0, "G": 255, "B": 255, "A": 255} 17 | }, 18 | "InterpolateStatesSpeed": 0 19 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/list_panel.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 4, "Y": 132, "Width": 88, "Height": 88}, 6 | "ExternalSourceRect": {"X": 0, "Y": 128, "Width": 96, "Height": 96} 7 | }, 8 | "Padding": {"Left": 18, "Right": 18, "Top": 12, "Bottom": 4}, 9 | "MarginAfter": {"X": 0, "Y": 6} 10 | }, 11 | "Disabled": { 12 | "EffectIdentifier": "disabled" 13 | }, 14 | "InterpolateStatesSpeed": 5 15 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/message_box_backdrop.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "BackgroundColor": { 4 | "R": 0, 5 | "G": 0, 6 | "B": 0, 7 | "A": 150 8 | } 9 | }, 10 | "Disabled": { 11 | "EffectIdentifier": "disabled" 12 | }, 13 | "InterpolateStatesSpeed": 2.5 14 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/numeric_input.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "text_input.json", 3 | "Default": { 4 | "TextAlignment": "Center", 5 | "Padding": {"Left": 2, "Right": 2, "Top": 6, "Bottom": 6} 6 | }, 7 | "DefaultTextAnchor": "Center" 8 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/numeric_input_button.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "button.json", 3 | "Default": { 4 | "MarginBefore": {"X": 4, "Y": 0}, 5 | "MarginAfter": {"X": 4, "Y": 0} 6 | }, 7 | "DefaultWidth": {"Units": "Pixels", "Value": 40}, 8 | "DefaultHeight": {"Units": "Pixels", "Value": 40} 9 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/panel.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | "ExternalSourceRect": {"X": 16, "Y": 0, "Width": 96, "Height": 96}, 5 | "FrameWidth": {"X": 4, "Y": 4} 6 | }, 7 | "Padding": {"Left": 18, "Right": 18, "Top": 14, "Bottom": 14} 8 | }, 9 | "Disabled": { 10 | "EffectIdentifier": "disabled" 11 | }, 12 | "InterpolateStatesSpeed": 5 13 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/panel_title.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 4, "Y": 98, "Width": 72, "Height": 14}, 6 | "ExternalSourceRect": {"X": 0, "Y": 98, "Width": 80, "Height": 14}, 7 | "Offset": {"X": 0, "Y": 0} 8 | }, 9 | "Padding": {"Left": 6, "Right": 6, "Top": 0, "Bottom": 0}, 10 | "ExtraSize": {"Left": 60, "Right": 60, "Top": 0, "Bottom": 0}, 11 | "TextFillColor": {"R": 0, "G": 255, "B": 255, "A": 255}, 12 | "TextOutlineColor": {"R": 0, "G": 0, "B": 0, "A": 255}, 13 | "MarginAfter": {"X": 0, "Y": 20}, 14 | "FontSize": 22, 15 | "TextSpacing": 1.75, 16 | "TextOutlineWidth": 2, 17 | "TextAlignment": "Center" 18 | }, 19 | "Disabled": { 20 | "EffectIdentifier": "disabled" 21 | } 22 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/paragraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "MarginAfter": {"X": 0, "Y": 6} 5 | } 6 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/paragraph_base_style.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "TextFillColor": {"R": 255, "G": 255, "B": 255, "A": 255}, 4 | "TextOutlineColor": {"R": 0, "G": 0, "B": 0, "A": 255}, 5 | "NoValueTextFillColor": {"R": 175, "G": 175, "B": 175, "A": 255}, 6 | "FontSize": 22, 7 | "TextSpacing": 2, 8 | "TextOutlineWidth": 2, 9 | "TextAlignment": "Left" 10 | }, 11 | "Targeted": { 12 | "TextFillColor": {"R": 0, "G": 255, "B": 255, "A": 255} 13 | }, 14 | "Interacted": { 15 | "TextFillColor": {"R": 0, "G": 255, "B": 255, "A": 255} 16 | }, 17 | "TargetedChecked": { 18 | "TextFillColor": {"R": 0, "G": 255, "B": 255, "A": 255} 19 | }, 20 | "Checked": { 21 | "TextFillColor": {"R": 255, "G": 255, "B": 255, "A": 255} 22 | }, 23 | "Disabled": { 24 | "EffectIdentifier": "disabled" 25 | } 26 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/progress_bar_horizontal.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 101, "Y": 130, "Width": 70, "Height": 12}, 6 | "ExternalSourceRect": {"X": 96, "Y": 128, "Width": 80, "Height": 16} 7 | }, 8 | "Padding": {"Left": 2, "Right": 2, "Top": 0, "Bottom": 0}, 9 | "MarginBefore": {"X": 0, "Y": 18}, 10 | "MarginAfter": {"X": 0, "Y": 18} 11 | }, 12 | "Disabled": { 13 | "EffectIdentifier": "disabled" 14 | }, 15 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 16 | "DefaultHeight": {"Value": 32, "Units": "Pixels"}, 17 | "InterpolateStatesSpeed": 5, 18 | "InterpolateOffsetsSpeed": 10 19 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/progress_bar_horizontal_alt.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 102, "Y": 144, "Width": 14, "Height": 12}, 6 | "ExternalSourceRect": {"X": 96, "Y": 144, "Width": 26, "Height": 12} 7 | }, 8 | "Padding": {"Left": 0, "Right": 0, "Top": 0, "Bottom": 0}, 9 | "MarginBefore": {"X": 0, "Y": 18}, 10 | "MarginAfter": {"X": 0, "Y": 18} 11 | }, 12 | "Targeted": { 13 | "FillTextureFramed": { 14 | 15 | "InternalSourceRect": {"X": 102, "Y": 156, "Width": 14, "Height": 12}, 16 | "ExternalSourceRect": {"X": 96, "Y": 156, "Width": 26, "Height": 12} 17 | } 18 | }, 19 | "Disabled": { 20 | "EffectIdentifier": "disabled" 21 | }, 22 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 23 | "DefaultHeight": {"Value": 32, "Units": "Pixels"}, 24 | "InterpolateStatesSpeed": 5 25 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/progress_bar_horizontal_alt_fill.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 102, "Y": 168, "Width": 14, "Height": 12}, 6 | "ExternalSourceRect": {"X": 96, "Y": 168, "Width": 26, "Height": 12} 7 | }, 8 | "Padding": {"Left": 0, "Right": 0, "Top": 0, "Bottom": 0}, 9 | "MarginBefore": {"X": 0, "Y": 18}, 10 | "MarginAfter": {"X": 0, "Y": 18} 11 | }, 12 | "Disabled": { 13 | "EffectIdentifier": "disabled" 14 | }, 15 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 16 | "DefaultHeight": {"Value": 32, "Units": "Pixels"}, 17 | "InterpolateStatesSpeed": 0, 18 | "InterpolateOffsetsSpeed": 0, 19 | "MinWidth": 30, 20 | "DefaultAnchor": "CenterLeft" 21 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/progress_bar_horizontal_fill.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 133, "Y": 114, "Width": 70, "Height": 12}, 6 | "ExternalSourceRect": {"X": 128, "Y": 112, "Width": 80, "Height": 16} 7 | }, 8 | "Padding": {"Left": 40, "Right": 40, "Top": 0, "Bottom": 0}, 9 | "MarginBefore": {"X": 0, "Y": 18}, 10 | "MarginAfter": {"X": 0, "Y": 18}, 11 | "TintColor": {"R": 155, "G": 255, "B": 255, "A": 255} 12 | }, 13 | "Disabled": { 14 | "EffectIdentifier": "disabled" 15 | }, 16 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 17 | "DefaultHeight": {"Value": 32, "Units": "Pixels"}, 18 | "InterpolateStatesSpeed": 5, 19 | "InterpolateOffsetsSpeed": 10, 20 | "MinWidth": 30, 21 | "DefaultAnchor": "CenterLeft" 22 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/radio_button.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "checkbox.json", 3 | "Default": { 4 | "Icon": { 5 | 6 | "SourceRect": {"X": 80, "Y": 112, "Width": 16, "Height": 16}, 7 | "CenterVertically": true 8 | } 9 | }, 10 | "Targeted": { 11 | "Icon": { 12 | 13 | "SourceRect": {"X": 112, "Y": 112, "Width": 16, "Height": 16}, 14 | "CenterVertically": true 15 | } 16 | }, 17 | "Interacted": { 18 | "Icon": { 19 | 20 | "SourceRect": {"X": 96, "Y": 112, "Width": 16, "Height": 16}, 21 | "CenterVertically": true 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/scrollbar_vertical.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 192, "Y": 12, "Width": 16, "Height": 72}, 6 | "ExternalSourceRect": {"X": 192, "Y": 0, "Width": 16, "Height": 96} 7 | }, 8 | "Padding": {"Left": 0, "Right": 0, "Top": 70, "Bottom": 70}, 9 | "MarginBefore": {"X": 10, "Y": 10}, 10 | "MarginAfter": {"X": 10, "Y": 10} 11 | }, 12 | "Disabled": { 13 | "EffectIdentifier": "disabled" 14 | }, 15 | "DefaultWidth": {"Value": 32, "Units": "Pixels"}, 16 | "DefaultHeight": {"Value": 100, "Units": "PercentOfParent"}, 17 | "InterpolateStatesSpeed": 5, 18 | "InterpolateOffsetsSpeed": 10 19 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/scrollbar_vertical_handle.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "ExternalSourceRect": {"X": 208, "Y": 0, "Width": 16, "Height": 48}, 6 | "InternalSourceRect": {"X": 208, "Y": 12, "Width": 16, "Height": 24} 7 | } 8 | }, 9 | "Targeted": { 10 | "FillTextureFramed": { 11 | 12 | "ExternalSourceRect": {"X": 240, "Y": 0, "Width": 16, "Height": 48}, 13 | "InternalSourceRect": {"X": 240, "Y": 12, "Width": 16, "Height": 24} 14 | } 15 | }, 16 | "Interacted": { 17 | "FillTextureFramed": { 18 | 19 | "ExternalSourceRect": {"X": 240, "Y": 0, "Width": 16, "Height": 48}, 20 | "InternalSourceRect": {"X": 240, "Y": 12, "Width": 16, "Height": 24} 21 | } 22 | }, 23 | "Disabled": { 24 | "EffectIdentifier": "disabled" 25 | }, 26 | "DefaultWidth": {"Value": 32, "Units": "Pixels"}, 27 | "DefaultHeight": {"Value": 80, "Units": "Pixels"}, 28 | "InterpolateStatesSpeed": 5, 29 | "InterpolateOffsetsSpeed": 10 30 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/slider_handle.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "Icon": { 4 | 5 | "SourceRect": {"X": 139, "Y": 96, "Width": 11, "Height": 11} 6 | } 7 | }, 8 | "Interacted": { 9 | "Icon": { 10 | 11 | "SourceRect": {"X": 128, "Y": 96, "Width": 11, "Height": 11} 12 | } 13 | }, 14 | "Disabled": { 15 | "EffectIdentifier": "disabled" 16 | }, 17 | "DefaultWidth": {"Value": 22, "Units": "Pixels"}, 18 | "DefaultHeight": {"Value": 22, "Units": "Pixels"}, 19 | "InterpolateStatesSpeed": 5, 20 | "InterpolateOffsetsSpeed": 10 21 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/slider_horizontal.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 5, "Y": 112, "Width": 70, "Height": 7}, 6 | "ExternalSourceRect": {"X": 0, "Y": 112, "Width": 80, "Height": 7} 7 | }, 8 | "Padding": {"Left": 40, "Right": 40, "Top": 0, "Bottom": 0}, 9 | "MarginBefore": {"X": 0, "Y": 18}, 10 | "MarginAfter": {"X": 0, "Y": 18} 11 | }, 12 | "Targeted": { 13 | "FillTextureFramed": { 14 | 15 | "InternalSourceRect": {"X": 5, "Y": 119, "Width": 70, "Height": 7}, 16 | "ExternalSourceRect": {"X": 0, "Y": 119, "Width": 80, "Height": 7} 17 | } 18 | }, 19 | "Interacted": { 20 | "FillTextureFramed": { 21 | 22 | "InternalSourceRect": {"X": 5, "Y": 119, "Width": 70, "Height": 7}, 23 | "ExternalSourceRect": {"X": 0, "Y": 119, "Width": 80, "Height": 7} 24 | } 25 | }, 26 | "Disabled": { 27 | "EffectIdentifier": "disabled" 28 | }, 29 | "DefaultWidth": {"Value": 100, "Units": "PercentOfParent"}, 30 | "DefaultHeight": {"Value": 14, "Units": "Pixels"}, 31 | "InterpolateStatesSpeed": 5, 32 | "InterpolateOffsetsSpeed": 10 33 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/slider_vertical.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | 5 | "InternalSourceRect": {"X": 215, "Y": 53, "Width": 7, "Height": 70}, 6 | "ExternalSourceRect": {"X": 215, "Y": 48, "Width": 7, "Height": 80} 7 | }, 8 | "Padding": {"Left": 0, "Right": 0, "Top": 40, "Bottom": 40}, 9 | "MarginBefore": {"X": 18, "Y": 10}, 10 | "MarginAfter": {"X": 18, "Y": 10} 11 | }, 12 | "Targeted": { 13 | "FillTextureFramed": { 14 | 15 | "InternalSourceRect": {"X": 208, "Y": 53, "Width": 7, "Height": 70}, 16 | "ExternalSourceRect": {"X": 208, "Y": 48, "Width": 7, "Height": 80} 17 | } 18 | }, 19 | "Interacted": { 20 | "FillTextureFramed": { 21 | 22 | "InternalSourceRect": {"X": 208, "Y": 53, "Width": 7, "Height": 70}, 23 | "ExternalSourceRect": {"X": 208, "Y": 48, "Width": 7, "Height": 80} 24 | } 25 | }, 26 | "Disabled": { 27 | "EffectIdentifier": "disabled" 28 | }, 29 | "DefaultWidth": {"Value": 14, "Units": "Pixels"}, 30 | "DefaultHeight": {"Value": 100, "Units": "PercentOfParent"}, 31 | "InterpolateStatesSpeed": 5, 32 | "InterpolateOffsetsSpeed": 10 33 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/text_input.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "FillTextureFramed": { 5 | 6 | "InternalSourceRect": {"X": 4, "Y": 132, "Width": 88, "Height": 88}, 7 | "ExternalSourceRect": {"X": 0, "Y": 128, "Width": 96, "Height": 96} 8 | }, 9 | "Padding": {"Left": 6, "Right": 6, "Top": 6, "Bottom": 6} 10 | }, 11 | "Targeted": { 12 | "TextFillColor": {"R": 255, "G": 255, "B": 255, "A": 255} 13 | }, 14 | "Interacted": { 15 | "TextFillColor": {"R": 255, "G": 255, "B": 255, "A": 255} 16 | }, 17 | "DefaultHeight": {"Value": 48, "Units": "Pixels"}, 18 | "InterpolateStatesSpeed": 5 19 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/title.json: -------------------------------------------------------------------------------- 1 | { 2 | "InheritFrom": "paragraph_base_style.json", 3 | "Default": { 4 | "TextFillColor": {"R": 0, "G": 255, "B": 255, "A": 255}, 5 | "TextScale": 1.2, 6 | "TextOutlineWidth": 3, 7 | "TextSpacing": 3, 8 | "TextAlignment": "Center" 9 | }, 10 | "DefaultTextAnchor": "AutoCenter" 11 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Styles/vertical_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "FillTextureFramed": { 4 | "InternalSourceRect": {"X": 222, "Y": 51, "Width": 2, "Height": 74}, 5 | "ExternalSourceRect": {"X": 222, "Y": 48, "Width": 2, "Height": 80} 6 | }, 7 | "Padding": {"Left": 0, "Right": 0, "Top": 8, "Bottom": 8}, 8 | "MarginBefore": {"X": 6, "Y": 0}, 9 | "MarginAfter": {"X": 6, "Y": 0} 10 | }, 11 | "Disabled": { 12 | "EffectIdentifier": "disabled" 13 | }, 14 | "DefaultWidth": {"Value": 8, "Units": "Pixels"}, 15 | "DefaultHeight": {"Value": 100, "Units": "PercentOfParent"} 16 | } -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Textures/Icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/Iguina.Demo/Assets/DefaultTheme/Textures/Icons.png -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/Textures/UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/Iguina.Demo/Assets/DefaultTheme/Textures/UI.png -------------------------------------------------------------------------------- /Iguina.Demo/Assets/DefaultTheme/system_style.json: -------------------------------------------------------------------------------- 1 | { 2 | "ThemeIdentifier": "Default UI", 3 | "CursorDefault": { 4 | 5 | "SourceRect": {"X": 0, "Y": 0, "Width": 16, "Height": 16} 6 | }, 7 | "CursorDisabled": { 8 | 9 | "SourceRect": {"X": 0, "Y": 16, "Width": 16, "Height": 16} 10 | }, 11 | "CursorLocked": { 12 | 13 | "SourceRect": {"X": 0, "Y": 16, "Width": 16, "Height": 16} 14 | }, 15 | "CursorInteractable": { 16 | 17 | "SourceRect": {"X": 0, "Y": 32, "Width": 16, "Height": 16} 18 | }, 19 | "FocusedEntityOverlay": { 20 | 21 | "InternalSourceRect": {"X": 177, "Y": 129, "Width": 30, "Height": 30}, 22 | "ExternalSourceRect": {"X": 176, "Y": 128, "Width": 32, "Height": 32} 23 | }, 24 | "RowSpaceHeight": 14, 25 | "DefaultTexture": "Textures/UI.png", 26 | "TextScale": 1, 27 | "TextureScale": 2, 28 | "CursorScale": 1, 29 | "TimeToLockInteractiveState": 0.135, 30 | "SystemIcons": { 31 | "file": { 32 | 33 | "SourceRect": {"X": 240, "Y": 48, "Width": 16, "Height": 16} 34 | }, 35 | "folder": { 36 | 37 | "SourceRect": {"X": 240, "Y": 64, "Width": 16, "Height": 16} 38 | } 39 | }, 40 | "LoadDefaultStylesheets": { 41 | "Panels": "Styles/panel.json", 42 | "MessageBoxBackdrop": "Styles/message_box_backdrop.json", 43 | "Paragraphs": "Styles/paragraph.json", 44 | "Titles": "Styles/title.json", 45 | "Labels": "Styles/label.json", 46 | "Buttons": "Styles/button.json", 47 | "HorizontalLines": "Styles/horizontal_line.json", 48 | "VerticalLines": "Styles/vertical_line.json", 49 | "CheckBoxes": "Styles/checkbox.json", 50 | "RadioButtons": "Styles/radio_button.json", 51 | "HorizontalSliders": "Styles/slider_horizontal.json", 52 | "VerticalSliders": "Styles/slider_vertical.json", 53 | "HorizontalSlidersHandle": "Styles/slider_handle.json", 54 | "VerticalSlidersHandle": "Styles/slider_handle.json", 55 | "ListPanels": "Styles/list_panel.json", 56 | "ListItems": "Styles/list_item.json", 57 | "DropDownPanels": "Styles/list_panel.json", 58 | "DropDownItems": "Styles/list_item.json", 59 | "DropDownIcon": "Styles/dropdown_icon.json", 60 | "VerticalScrollbars": "Styles/scrollbar_vertical.json", 61 | "VerticalScrollbarsHandle": "Styles/scrollbar_vertical_handle.json", 62 | "TextInput": "Styles/text_input.json", 63 | "NumericTextInput": "Styles/numeric_input.json", 64 | "NumericTextInputButton": "Styles/numeric_input_button.json", 65 | "HorizontalProgressBars": "Styles/progress_bar_horizontal.json", 66 | "HorizontalProgressBarsFill": "Styles/progress_bar_horizontal_fill.json", 67 | "HorizontalColorSliders": "Styles/color_slider_horizontal.json", 68 | "HorizontalColorSlidersHandle": "Styles/color_slider_horizontal_handle.json", 69 | "ColorPickers": "Styles/color_picker.json", 70 | "ColorPickersHandle": "Styles/color_picker_handle.json" 71 | } 72 | } -------------------------------------------------------------------------------- /Iguina.Demo/Iguina.Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Iguina.Demo/readme.md: -------------------------------------------------------------------------------- 1 | # Iguina.Demo 2 | 3 | Implementation-agnostic demo project. 4 | This project loads the stylesheets and creates all the demo project entities. 5 | It requires drivers from the host application for rendering and input. 6 | 7 | ## Assets 8 | 9 | In assets you can find built-in UI themes that are used by the demo application. 10 | 11 | For full documentation, check out the readme file at the repository root. -------------------------------------------------------------------------------- /Iguina.Tests/Iguina.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Iguina.Tests/NumericInputTests.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Entities; 2 | 3 | namespace Iguina.Tests 4 | { 5 | /// 6 | /// Tests for . 7 | /// 8 | public class NumericInputTests 9 | { 10 | [TestCase("5", 5, "5")] 11 | [TestCase("100", 100, "100")] 12 | [TestCase("0", 0, "0")] 13 | [TestCase("00", 0, "0")] 14 | [TestCase("000", 0, "0")] 15 | [TestCase("05", 5, "5")] 16 | [TestCase("005", 5, "5")] 17 | [TestCase("00500", 500, "500")] 18 | [TestCase("1.23", 1.23, "1.23")] 19 | [TestCase("0.23", 0.23, "0.23")] 20 | [TestCase("0.04", 0.04, "0.04")] 21 | [TestCase("0.004", 0.004, "0.004")] 22 | [TestCase("00.2", 0.2, "0.2")] 23 | [TestCase("000.2", 0.2, "0.2")] 24 | [TestCase("23.0", 23.0, "23.0")] 25 | [TestCase("23.00", 23.0, "23.00")] 26 | [TestCase("23.000", 23.0, "23.000")] 27 | [TestCase("02.3", 2.3, "2.3")] 28 | [TestCase("20.3", 20.3, "20.3")] 29 | [TestCase("0020.3", 20.3, "20.3")] 30 | [TestCase(".23", 0.23, "0.23")] 31 | [TestCase("2.", 2, "2.")] 32 | [TestCase("-5", -5, "-5")] 33 | [TestCase("-100", -100, "-100")] 34 | [TestCase("-0", -0, "-0")] 35 | [TestCase("-00", -0, "-0")] 36 | [TestCase("-000", -0, "-0")] 37 | [TestCase("-05", -5, "-5")] 38 | [TestCase("-005", -5, "-5")] 39 | [TestCase("-00500", -500, "-500")] 40 | [TestCase("-1.23", -1.23, "-1.23")] 41 | [TestCase("-0.23", -0.23, "-0.23")] 42 | [TestCase("-0.04", -0.04, "-0.04")] 43 | [TestCase("-0.004", -0.004, "-0.004")] 44 | [TestCase("-00.2", -0.2, "-0.2")] 45 | [TestCase("-000.2", -0.2, "-0.2")] 46 | [TestCase("-23.0", -23.0, "-23.0")] 47 | [TestCase("-23.00", -23.0, "-23.00")] 48 | [TestCase("-23.000", -23.0, "-23.000")] 49 | [TestCase("-0023.0", -23.0, "-23.0")] 50 | [TestCase("-.23", -0.23, "-0.23")] 51 | [TestCase("-2.", -2, "-2.")] 52 | [TestCase("-", 0, "-")] 53 | [TestCase("-01.00", -1.0, "-1.00")] 54 | [TestCase("-001.00", -1.0, "-1.00")] 55 | [TestCase("0.", 0, "0.")] 56 | [TestCase("0.0", 0, "0.0")] 57 | [TestCase("0.000", 0, "0.000")] 58 | [TestCase("00.0", 0, "0.0")] 59 | [TestCase("000.0", 0, "0.0")] 60 | [TestCase("000.000", 0, "0.000")] 61 | [TestCase(".", 0, "0.")] 62 | [TestCase(".0", 0, "0.0")] 63 | [TestCase(".000", 0, "0.000")] 64 | [TestCase("-0.", 0, "-0.")] 65 | [TestCase("-0.0", 0, "-0.0")] 66 | [TestCase("-0.000", 0, "-0.000")] 67 | [TestCase("-00.0", 0, "-0.0")] 68 | [TestCase("-000.0", 0, "-0.0")] 69 | [TestCase("-000.000", 0, "-0.000")] 70 | [TestCase("-.", 0, "-0.")] 71 | [TestCase("-.0", 0, "-0.0")] 72 | [TestCase("-.000", 0, "-0.000")] 73 | [TestCase("", 0, "")] 74 | [TestCase("x", 0, "")] 75 | public void TestEnterDecimalNumber(string input, decimal expectedValue, string? expectedText = null) 76 | { 77 | if (expectedText == null) 78 | expectedText = input; 79 | 80 | var system = new UISystem(new TestRenderer(), new TestInputProvider()); 81 | 82 | var numericInput = new NumericInput(system); 83 | 84 | // Add each character on at a time, as if the user is typing it 85 | 86 | for (int i = 0; i < input.Length; i++) 87 | numericInput.Value += input[i]; 88 | 89 | Assert.That(numericInput.NumericValue, Is.EqualTo(expectedValue), "Typed value mismatch"); 90 | Assert.That(numericInput.Value, Is.EqualTo(expectedText), "Typed string mismatch"); 91 | 92 | // Add the whole text at once 93 | 94 | numericInput = new NumericInput(system); 95 | 96 | numericInput.Value = input; 97 | 98 | Assert.That(numericInput.NumericValue, Is.EqualTo(expectedValue), "Set value mismatch"); 99 | Assert.That(numericInput.Value, Is.EqualTo(expectedText), "Set string mismatch"); 100 | } 101 | 102 | // todo: integer tests, i.e. not accepting decimal 103 | } 104 | } -------------------------------------------------------------------------------- /Iguina.Tests/TestInputProvider.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Drivers; 3 | 4 | namespace Iguina.Tests 5 | { 6 | public class TestInputProvider : IInputProvider 7 | { 8 | public Point GetMousePosition() 9 | { 10 | return new Point(200, 100); 11 | } 12 | 13 | public bool IsMouseButtonDown(MouseButton btn) 14 | { 15 | return false; 16 | } 17 | 18 | public int GetMouseWheelChange() 19 | { 20 | return 0; 21 | } 22 | 23 | public int[] GetTextInput() 24 | { 25 | return []; 26 | } 27 | 28 | public TextInputCommands[] GetTextInputCommands() 29 | { 30 | return []; 31 | } 32 | 33 | public KeyboardInteractions? GetKeyboardInteraction() 34 | { 35 | return null; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Iguina.Tests/TestRenderer.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Drivers; 3 | 4 | namespace Iguina.Tests 5 | { 6 | public class TestRenderer : IRenderer 7 | { 8 | private Rectangle? _scissorRegion; 9 | 10 | 11 | public Rectangle GetScreenBounds() 12 | { 13 | return new Rectangle(0, 0, 1280, 720); 14 | } 15 | 16 | public void DrawTexture(string? effectIdentifier, string textureId, Rectangle destRect, Rectangle sourceRect, Color color) 17 | { 18 | } 19 | 20 | public Point MeasureText(string text, string? fontId, int fontSize, float spacing) 21 | { 22 | return new Point(text.Length * 10, 20); 23 | } 24 | 25 | public int GetTextLineHeight(string? fontId, int fontSize) 26 | { 27 | return 20; 28 | } 29 | 30 | public void DrawText(string? effectIdentifier, string text, string? fontId, int fontSize, Point position, Color fillColor, Color outlineColor, int outlineWidth, float spacing) 31 | { 32 | } 33 | 34 | public void DrawRectangle(Rectangle rectangle, Color color) 35 | { 36 | } 37 | 38 | public void SetScissorRegion(Rectangle region) 39 | { 40 | _scissorRegion = region; 41 | } 42 | 43 | public Rectangle? GetScissorRegion() 44 | { 45 | return _scissorRegion; 46 | } 47 | 48 | public void ClearScissorRegion() 49 | { 50 | _scissorRegion = null; 51 | } 52 | 53 | public Color GetPixelFromTexture(string textureId, Point sourcePosition) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public Point? FindPixelOffsetInTexture(string textureId, Rectangle sourceRect, Color color, bool returnNearestColor) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Iguina.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34031.279 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Iguina.Demo.RayLib", "Iguina.Demo.RayLib\Iguina.Demo.RayLib.csproj", "{ECDC699B-77D7-41D4-B6E9-41B62028AFF9}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Iguina", "Iguina\Iguina.csproj", "{32F4BCCC-EB5D-4C12-9237-2D11373B331B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Iguina.Demo", "Iguina.Demo\Iguina.Demo.csproj", "{1374D1D9-8F36-4068-8612-F01D993EADDA}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Iguina.Demo.MonoGame", "Iguina.Demo.MonoGame\Iguina.Demo.MonoGame.csproj", "{E85ECED4-439E-428F-B56F-32D78609CCB1}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Iguina.Tests", "Iguina.Tests\Iguina.Tests.csproj", "{B9B47536-6B18-41CF-9213-F7EFA851483D}" 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 | {ECDC699B-77D7-41D4-B6E9-41B62028AFF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {ECDC699B-77D7-41D4-B6E9-41B62028AFF9}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {ECDC699B-77D7-41D4-B6E9-41B62028AFF9}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {ECDC699B-77D7-41D4-B6E9-41B62028AFF9}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {32F4BCCC-EB5D-4C12-9237-2D11373B331B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {32F4BCCC-EB5D-4C12-9237-2D11373B331B}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {32F4BCCC-EB5D-4C12-9237-2D11373B331B}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {32F4BCCC-EB5D-4C12-9237-2D11373B331B}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {1374D1D9-8F36-4068-8612-F01D993EADDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {1374D1D9-8F36-4068-8612-F01D993EADDA}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {1374D1D9-8F36-4068-8612-F01D993EADDA}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {1374D1D9-8F36-4068-8612-F01D993EADDA}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {E85ECED4-439E-428F-B56F-32D78609CCB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {E85ECED4-439E-428F-B56F-32D78609CCB1}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {E85ECED4-439E-428F-B56F-32D78609CCB1}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {E85ECED4-439E-428F-B56F-32D78609CCB1}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {B9B47536-6B18-41CF-9213-F7EFA851483D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {B9B47536-6B18-41CF-9213-F7EFA851483D}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {B9B47536-6B18-41CF-9213-F7EFA851483D}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {B9B47536-6B18-41CF-9213-F7EFA851483D}.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 = {B1C8F349-FC49-4EBB-BAD9-9CA93DB2B845} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /Iguina/Defs/Anchor.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// Positions in parent entity to position the entity from. 7 | /// The anchor also affect the UI entity's offset. 8 | /// 9 | public enum Anchor 10 | { 11 | /// 12 | /// Auto placement with one entity per row. 13 | /// Going left-to-right. 14 | /// 15 | AutoLTR, 16 | 17 | /// 18 | /// Auto placement in the same row, until exceeding parent width. 19 | /// Going left-to-right. 20 | /// 21 | AutoInlineLTR, 22 | 23 | /// 24 | /// Auto placement with one entity per row. 25 | /// Going right-to-left. 26 | /// 27 | AutoRTL, 28 | 29 | /// 30 | /// Auto placement in the same row, until exceeding parent width. 31 | /// Going right-to-left. 32 | /// 33 | AutoInlineRTL, 34 | 35 | /// 36 | /// Auto placement with one entity per row. 37 | /// Aligned to center. 38 | /// 39 | AutoCenter, 40 | 41 | /// 42 | /// Entity is aligned to parent top-left internal corner. 43 | /// 44 | TopLeft, 45 | 46 | /// 47 | /// Entity is aligned to parent top-center internal point. 48 | /// 49 | TopCenter, 50 | 51 | /// 52 | /// Entity is aligned to parent top-right internal corner. 53 | /// 54 | TopRight, 55 | 56 | /// 57 | /// Entity is aligned to parent bottom-left internal corner. 58 | /// 59 | BottomLeft, 60 | 61 | /// 62 | /// Entity is aligned to parent bottom-center internal point. 63 | /// 64 | BottomCenter, 65 | 66 | /// 67 | /// Entity is aligned to parent bottom-right internal corner. 68 | /// 69 | BottomRight, 70 | 71 | /// 72 | /// Entity is aligned to parent center-left internal point. 73 | /// 74 | CenterLeft, 75 | 76 | /// 77 | /// Entity is aligned to parent center internal point. 78 | /// 79 | Center, 80 | 81 | /// 82 | /// Entity is aligned to parent center-right internal point. 83 | /// 84 | CenterRight, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Iguina/Defs/Color.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Globalization; 3 | 4 | namespace Iguina.Defs 5 | { 6 | /// 7 | /// Serializable color value. 8 | /// 9 | public struct Color 10 | { 11 | public byte R { get; set; } 12 | public byte G { get; set; } 13 | public byte B { get; set; } 14 | public byte A { get; set; } 15 | 16 | /// 17 | /// Parse color value from string. 18 | /// 19 | /// Color as string, in format RRGGBB or RRGGBBAA. 20 | /// Parsed color. 21 | public static Color Parse(string hex) 22 | { 23 | byte r, g, b, a = 255; 24 | 25 | if (hex.Length == 6) 26 | { 27 | r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); 28 | g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); 29 | b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); 30 | } 31 | else if (hex.Length == 8) 32 | { 33 | r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); 34 | g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); 35 | b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); 36 | a = byte.Parse(hex.Substring(6, 2), NumberStyles.HexNumber); 37 | } 38 | else 39 | { 40 | throw new ArgumentException("Invalid hex color format"); 41 | } 42 | 43 | return new Color(r, g, b, a); 44 | } 45 | 46 | public Color() 47 | { 48 | } 49 | 50 | public Color(byte r, byte g, byte b, byte a) 51 | { 52 | R = r; 53 | G = g; 54 | B = b; 55 | A = a; 56 | } 57 | 58 | public static readonly Color White = new Color(255, 255, 255, 255); 59 | public static readonly Color Black = new Color(0, 0, 0, 255); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Iguina/Defs/CursorProperties.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// Describe how to render the cursor. 7 | /// 8 | public class CursorProperties 9 | { 10 | /// 11 | /// Texture identifier. 12 | /// 13 | public string TextureId { get; set; } = null!; 14 | 15 | /// 16 | /// The source rectangle of the cursor texture to draw. 17 | /// An empty rectangle (0, 0, 0, 0) will render the entire texture. 18 | /// 19 | public Rectangle SourceRect { get; set; } 20 | 21 | /// 22 | /// Fill color tint. 23 | /// 24 | public Color FillColor { get; set; } = new Color(255, 255, 255, 255); 25 | 26 | /// 27 | /// Effect to use while rendering the cursor. 28 | /// This can be used by the host application as actual shader name, a flag identifier to change rendering, or any other purpose. 29 | /// 30 | public string EffectIdentifier { get; set; } = null!; 31 | 32 | /// 33 | /// Cursor graphics offset from top-left corner, in pixels. 34 | /// 35 | public Point Offset { get; set; } 36 | 37 | /// 38 | /// Cursor scale. 39 | /// 40 | public float Scale { get; set; } = 1f; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Iguina/Defs/EntityState.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// Different states an entity can be in. 7 | /// 8 | public enum EntityState 9 | { 10 | /// 11 | /// Default inactive state. 12 | /// 13 | Default, 14 | 15 | /// 16 | /// Entity is currently being targeted. For example, the mouse points on the entity. 17 | /// 18 | Targeted, 19 | 20 | 21 | 22 | /// 23 | /// Entity is currently being interacted with, for example text input entity we're currently typing into, or a button that is being pressed. 24 | /// 25 | Interacted, 26 | 27 | /// 28 | /// Entity is checked. For checkbox, radio button, or other entities that have a 'checked' state. 29 | /// 30 | Checked, 31 | 32 | /// 33 | /// Entity is currently being targeted, and also checked. For example, the mouse points on the entity. 34 | /// 35 | TargetedChecked, 36 | 37 | /// 38 | /// Entity is currently focused, ie its the last entity the user interacted with, and will accept keyboard interactions. 39 | /// 40 | Focused, 41 | 42 | /// 43 | /// Entity is disabled. 44 | /// 45 | Disabled, 46 | 47 | /// 48 | /// Entity is disabled but also checked. 49 | /// 50 | DisabledChecked, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Iguina/Defs/FramedTexture.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// Define a texture with internal source rect and frame source rect. 7 | /// When rendered on a region, it will tile-render the frame parts on the edges, and the center part repeating in the fill. 8 | /// 9 | public class FramedTexture 10 | { 11 | /// 12 | /// Texture identifier. 13 | /// 14 | public string TextureId { get; set; } = null!; 15 | 16 | /// 17 | /// The source rectangle of the center part, without the frame. 18 | /// 19 | public Rectangle InternalSourceRect 20 | { 21 | get 22 | { 23 | if (_internalSourceRect.Width == 0 && _internalSourceRect.Height == 0 && FrameWidth.HasValue) 24 | { 25 | _internalSourceRect = new Rectangle( 26 | ExternalSourceRect.X + FrameWidth.Value.X, 27 | ExternalSourceRect.Y + FrameWidth.Value.Y, 28 | ExternalSourceRect.Width - FrameWidth.Value.X * 2, 29 | ExternalSourceRect.Height - FrameWidth.Value.Y * 2); 30 | } 31 | return _internalSourceRect; 32 | } 33 | set => _internalSourceRect = value; 34 | } 35 | 36 | // internal source rect value 37 | Rectangle _internalSourceRect; 38 | 39 | /// 40 | /// Graphics frame width. 41 | /// If set, it will set the value of InternalSourceRect by calculating ExternalSourceRect - FrameWidth. 42 | /// 43 | public Point? FrameWidth 44 | { 45 | get => _frameWidth; 46 | set 47 | { 48 | _frameWidth = value; 49 | if (value.HasValue) 50 | { 51 | _internalSourceRect = new Rectangle(); 52 | } 53 | } 54 | } 55 | 56 | // frame width value 57 | Point? _frameWidth; 58 | 59 | /// 60 | /// The source rectangle of the entire framed texture, including the frame. 61 | /// 62 | public Rectangle ExternalSourceRect { get; set; } 63 | 64 | /// 65 | /// Get the source rectangle of the top frame, without corners. 66 | /// 67 | public Rectangle TopSourceRect => new Rectangle(InternalSourceRect.Left, ExternalSourceRect.Top, InternalSourceRect.Width, InternalSourceRect.Top - ExternalSourceRect.Top); 68 | 69 | /// 70 | /// Get the source rectangle of the bottom frame, without corners. 71 | /// 72 | public Rectangle BottomSourceRect => new Rectangle(InternalSourceRect.Left, InternalSourceRect.Bottom, InternalSourceRect.Width, ExternalSourceRect.Bottom - InternalSourceRect.Bottom); 73 | 74 | /// 75 | /// Get the source rectangle of the left frame, without corners. 76 | /// 77 | public Rectangle LeftSourceRect => new Rectangle(ExternalSourceRect.Left, InternalSourceRect.Top, InternalSourceRect.Left - ExternalSourceRect.Left, InternalSourceRect.Height); 78 | 79 | /// 80 | /// Get the source rectangle of the right frame, without corners. 81 | /// 82 | public Rectangle RightSourceRect => new Rectangle(InternalSourceRect.Right, InternalSourceRect.Top, ExternalSourceRect.Right - InternalSourceRect.Right, InternalSourceRect.Height); 83 | 84 | /// 85 | /// Get the source rectangle of the top left corner. 86 | /// 87 | public Rectangle TopLeftSourceRect => new Rectangle(ExternalSourceRect.Left, ExternalSourceRect.Top, InternalSourceRect.Left - ExternalSourceRect.Left, InternalSourceRect.Top - ExternalSourceRect.Top); 88 | 89 | /// 90 | /// Get the source rectangle of the top right corner. 91 | /// 92 | public Rectangle TopRightSourceRect => new Rectangle(InternalSourceRect.Right, ExternalSourceRect.Top, ExternalSourceRect.Right - InternalSourceRect.Right, InternalSourceRect.Top - ExternalSourceRect.Top); 93 | 94 | /// 95 | /// Get the source rectangle of the bottom left corner. 96 | /// 97 | public Rectangle BottomLeftSourceRect => new Rectangle(ExternalSourceRect.Left, InternalSourceRect.Bottom, InternalSourceRect.Left - ExternalSourceRect.Left, ExternalSourceRect.Bottom - InternalSourceRect.Bottom); 98 | 99 | /// 100 | /// Get the source rectangle of the bottom right corner. 101 | /// 102 | public Rectangle BottomRightSourceRect => new Rectangle(InternalSourceRect.Right, InternalSourceRect.Bottom, ExternalSourceRect.Right - InternalSourceRect.Right, ExternalSourceRect.Bottom - InternalSourceRect.Bottom); 103 | 104 | /// 105 | /// Will scale frame and source parts by this factor. 106 | /// 107 | public float TextureScale { get; set; } = 1f; 108 | 109 | /// 110 | /// Offset in pixels. 111 | /// 112 | public Point Offset { get; set; } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Iguina/Defs/IconTexture.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Defs 3 | { 4 | /// 5 | /// A texture to render as icon, with size based on source rectangle. 6 | /// 7 | public class IconTexture 8 | { 9 | /// 10 | /// Texture identifier. 11 | /// 12 | public string TextureId { get; set; } = null!; 13 | 14 | /// 15 | /// The source rectangle of the texture to draw. 16 | /// 17 | public Rectangle SourceRect { get; set; } 18 | 19 | /// 20 | /// Will scale icon by this factor. 21 | /// 22 | public float TextureScale { get; set; } = 1f; 23 | 24 | /// 25 | /// If true, will center icon horizontally. 26 | /// 27 | public bool CenterHorizontally { get; set; } 28 | 29 | /// 30 | /// If true, will center icon vertically. 31 | /// 32 | public bool CenterVertically { get; set; } 33 | 34 | /// 35 | /// Icon offset in pixels. 36 | /// 37 | public Point Offset { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Iguina/Defs/InputState.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using Iguina.Drivers; 4 | 5 | namespace Iguina.Defs 6 | { 7 | /// 8 | /// All input properties for entities interactions. 9 | /// 10 | public struct InputState 11 | { 12 | /// 13 | /// Current frame state. 14 | /// 15 | internal CurrentInputState _Current; 16 | 17 | /// 18 | /// Previous frame state. 19 | /// 20 | internal CurrentInputState _Previous; 21 | 22 | /// 23 | /// Is mouse left button is currently down. 24 | /// 25 | public bool LeftMouseDown => _Current.LeftMouseButton; 26 | 27 | /// 28 | /// Is mouse right button is currently down. 29 | /// 30 | public bool RightMouseDown => _Current.RightMouseButton; 31 | 32 | /// 33 | /// Is mouse wheel button is currently down. 34 | /// 35 | public bool WheelMouseDown => _Current.WheelMouseButton; 36 | 37 | /// 38 | /// Was left mouse button pressed this frame. 39 | /// 40 | public bool LeftMousePressedNow => _Current.LeftMouseButton && !_Previous.LeftMouseButton; 41 | 42 | /// 43 | /// Was right mouse button pressed this frame. 44 | /// 45 | public bool RightMousePressedNow => _Current.RightMouseButton && !_Previous.RightMouseButton; 46 | 47 | /// 48 | /// Was mouse wheel button pressed this frame. 49 | /// 50 | public bool WheelMousePressedNow => _Current.WheelMouseButton && !_Previous.WheelMouseButton; 51 | 52 | /// 53 | /// Was left mouse button released this frame. 54 | /// 55 | public bool LeftMouseReleasedNow => !_Current.LeftMouseButton && _Previous.LeftMouseButton; 56 | 57 | /// 58 | /// Was right mouse button released this frame. 59 | /// 60 | public bool RightMouseReleasedNow => !_Current.RightMouseButton && _Previous.RightMouseButton; 61 | 62 | /// 63 | /// Was mouse wheel button released this frame. 64 | /// 65 | public bool WheelMouseReleasedNow => !_Current.WheelMouseButton && _Previous.WheelMouseButton; 66 | 67 | /// 68 | /// Mouse wheel change. 69 | /// 70 | public int MouseWheelChange => _Current.MouseWheelChange; 71 | 72 | /// 73 | /// Current mouse position. 74 | /// 75 | public Point MousePosition => _Current.MousePosition; 76 | 77 | /// 78 | /// Mouse movement this frame. 79 | /// 80 | public Point MouseMove => new Point(_Current.MousePosition.X - _Previous.MousePosition.X, _Current.MousePosition.Y - _Previous.MousePosition.Y); 81 | 82 | /// 83 | /// Get current frame text input characters. 84 | /// 85 | public int[] TextInput => _Current.TextInput; 86 | 87 | /// 88 | /// Get current frame text input commands. 89 | /// 90 | public TextInputCommands[] TextInputCommands => _Current.TextInputCommands; 91 | 92 | /// 93 | /// Get current keyboard interactions. 94 | /// 95 | public KeyboardInteractions? KeyboardInteraction => (_Current.KeyboardInteraction != _Previous.KeyboardInteraction) ? _Current.KeyboardInteraction : null; 96 | 97 | /// 98 | /// Get if keyboard select button is currently held down. 99 | /// 100 | public bool IsKeyboardSelectPressedDown => _Current.KeyboardInteraction == KeyboardInteractions.Select; 101 | 102 | /// 103 | /// Current screen bounds. 104 | /// 105 | public Rectangle ScreenBounds; 106 | } 107 | 108 | /// 109 | /// Input state for a specific frame. 110 | /// 111 | public struct CurrentInputState 112 | { 113 | public bool LeftMouseButton; 114 | public bool RightMouseButton; 115 | public bool WheelMouseButton; 116 | public Point MousePosition; 117 | public int MouseWheelChange; 118 | public int[] TextInput; 119 | public TextInputCommands[] TextInputCommands; 120 | public KeyboardInteractions? KeyboardInteraction; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Iguina/Defs/MeasureUnits.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | 4 | namespace Iguina.Defs 5 | { 6 | /// 7 | /// Define a UI measurement unit. 8 | /// For example, position, size, etc. 9 | /// 10 | public struct Measurement 11 | { 12 | /// 13 | /// Measure value. 14 | /// 15 | public float Value { get; set; } 16 | 17 | /// 18 | /// Measure unit type. 19 | /// 20 | [JsonConverter(typeof(JsonStringEnumConverter))] 21 | public MeasureUnit Units { get; set; } 22 | 23 | /// 24 | /// Set value as pixels. 25 | /// 26 | public void SetPixels(int value) 27 | { 28 | Value = (float)value; 29 | Units = MeasureUnit.Pixels; 30 | } 31 | 32 | /// 33 | /// Set value as percents. 34 | /// 35 | public void SetPercents(float percent) 36 | { 37 | Value = Math.Max(0f, percent); 38 | Units = MeasureUnit.PercentOfParent; 39 | } 40 | 41 | /// 42 | /// Get value in pixels. 43 | /// 44 | /// Relevant parent size. 45 | /// Measurement value in pixels. 46 | public int GetValueInPixels(int parentSize) 47 | { 48 | if (Units == MeasureUnit.Pixels) { return (int)Value; } 49 | return (int)MathF.Round(parentSize * (Value / 100f)); 50 | } 51 | } 52 | 53 | /// 54 | /// GUI measure unit with X and Y components. 55 | /// 56 | public struct MeasureVector 57 | { 58 | /// 59 | /// Measure unit on X axis. 60 | /// 61 | public Measurement X; 62 | 63 | /// 64 | /// Measure unit on Y axis. 65 | /// 66 | public Measurement Y; 67 | 68 | /// 69 | /// Create and return measure vector with pixel values. 70 | /// 71 | public static MeasureVector FromPixels(int x, int y) 72 | { 73 | var ret = new MeasureVector(); 74 | ret.SetPixels(x, y); 75 | return ret; 76 | } 77 | 78 | /// 79 | /// Create and return measure vector with float values. 80 | /// 81 | public static MeasureVector FromPercents(float x, float y) 82 | { 83 | var ret = new MeasureVector(); 84 | ret.SetPercents(x, y); 85 | return ret; 86 | } 87 | 88 | /// 89 | /// Set value as pixels. 90 | /// 91 | public void SetPixels(int x, int y) 92 | { 93 | X.SetPixels(x); 94 | Y.SetPixels(y); 95 | } 96 | 97 | /// 98 | /// Set value as percent of parent size. 99 | /// 100 | public void SetPercents(float x, float y) 101 | { 102 | X.SetPercents(x); 103 | Y.SetPercents(y); 104 | } 105 | } 106 | 107 | /// 108 | /// Measure unit types. 109 | /// 110 | public enum MeasureUnit 111 | { 112 | /// 113 | /// Value is pixels. 114 | /// 115 | Pixels = 0, 116 | 117 | /// 118 | /// Value is percent of parent size. 119 | /// For example if the measure is talking about position x, it will be in perents of parent entity width. 120 | /// 121 | PercentOfParent = 1 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Iguina/Defs/Orientation.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// Defines entity orientation: vertical or horizontal. 7 | /// 8 | public enum Orientation 9 | { 10 | Horizontal, 11 | Vertical 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Iguina/Defs/OverflowMode.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Defs 3 | { 4 | /// 5 | /// Define how to handle child entities that go out of the entity's bounds. 6 | /// 7 | public enum OverflowMode 8 | { 9 | /// 10 | /// Child entities can overflow bounding rectangle freely. 11 | /// 12 | AllowOverflow, 13 | 14 | /// 15 | /// Child entities rendering will be cut off if they go out of bounding rectangle. 16 | /// 17 | HideOverflow, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Iguina/Defs/Point.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Defs 3 | { 4 | /// 5 | /// Serializable point value. 6 | /// 7 | public struct Point 8 | { 9 | public int X { get; set; } 10 | public int Y { get; set; } 11 | 12 | public Point() { } 13 | 14 | public Point(int x, int y) 15 | { 16 | X = x; 17 | Y = y; 18 | } 19 | 20 | public static readonly Point Zero = new Point(0, 0); 21 | 22 | public static readonly Point One = new Point(1, 1); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Iguina/Defs/Rectangle.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Defs 3 | { 4 | /// 5 | /// Serializable rectangle. 6 | /// 7 | public struct Rectangle 8 | { 9 | public int X { get; set; } 10 | public int Y { get; set; } 11 | public int Width { get; set; } 12 | public int Height { get; set; } 13 | 14 | public int Left => X; 15 | public int Top => Y; 16 | public int Right => X + Width; 17 | public int Bottom => Y + Height; 18 | public Point Center => new Point(X + Width / 2, Y + Height / 2); 19 | 20 | public static readonly Rectangle Empty = new Rectangle(); 21 | 22 | public Rectangle() { } 23 | 24 | public Rectangle(int x, int y, int width, int height) 25 | { 26 | X = x; 27 | Y = y; 28 | Width = width; 29 | Height = height; 30 | } 31 | 32 | /// 33 | /// Check if a point is container in rectangle. 34 | /// 35 | public bool Contains(Point point) 36 | { 37 | return point.X >= Left && point.X <= Right && point.Y >= Top && Y <= Bottom; 38 | } 39 | 40 | /// 41 | /// Merge two rectangles. 42 | /// 43 | public static Rectangle MergeRectangles(Rectangle rect1, Rectangle rect2) 44 | { 45 | int x1 = Math.Max(rect1.X, rect2.X); 46 | int y1 = Math.Max(rect1.Y, rect2.Y); 47 | int x2 = Math.Min(rect1.X + rect1.Width, rect2.X + rect2.Width); 48 | int y2 = Math.Min(rect1.Y + rect1.Height, rect2.Y + rect2.Height); 49 | 50 | int width = x2 - x1; 51 | int height = y2 - y1; 52 | 53 | if (width > 0 && height > 0) 54 | { 55 | return new Rectangle(x1, y1, width, height); 56 | } 57 | else 58 | { 59 | return new Rectangle(0, 0, 0, 0); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Iguina/Defs/Sides.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// Define 4 sides values. 7 | /// 8 | public struct Sides 9 | { 10 | public int Left { get; set; } 11 | public int Right { get; set; } 12 | public int Top { get; set; } 13 | public int Bottom { get; set; } 14 | 15 | public Sides() { } 16 | 17 | public Sides(int left, int right, int top, int bottom) 18 | { 19 | Left = left; 20 | Right = right; 21 | Top = top; 22 | Bottom = bottom; 23 | } 24 | 25 | public void TurnToZero() 26 | { 27 | Left = Right = Top = Bottom = 0; 28 | } 29 | 30 | public void Set(int left, int right, int top, int bottom) 31 | { 32 | Left = left; 33 | Right = right; 34 | Top = top; 35 | Bottom = bottom; 36 | } 37 | 38 | public static readonly Sides Zero = new Sides(0, 0, 0, 0); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Iguina/Defs/StretchedTexture.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// A texture to render stretched over the given region. 7 | /// 8 | public class StretchedTexture 9 | { 10 | /// 11 | /// Texture identifier. 12 | /// 13 | public string TextureId { get; set; } = null!; 14 | 15 | /// 16 | /// The source rectangle of the texture to draw. 17 | /// 18 | public Rectangle SourceRect { get; set; } 19 | 20 | /// 21 | /// Add extra size to the sides of this texture when rendering it. 22 | /// 23 | public Sides? ExtraSize { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Iguina/Defs/SystemStyleSheet.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace Iguina.Defs 4 | { 5 | /// 6 | /// System-level stylesheet for UI. 7 | /// This define general properties that are not related to any specific entity. 8 | /// 9 | public class SystemStyleSheet 10 | { 11 | /// 12 | /// UI Theme identifier. 13 | /// 14 | public string ThemeIdentifier { get; set; } = null!; 15 | 16 | /// 17 | /// Scale all fonts in the UI system by this factor. 18 | /// 19 | public float TextScale { get; set; } = 1f; 20 | 21 | /// 22 | /// Scale all textures in the UI system by this factor. 23 | /// 24 | public float TextureScale { get; set; } = 1f; 25 | 26 | /// 27 | /// Default texture to use when a texture is expected but not provided. 28 | /// 29 | public string DefaultTexture { get; set; } = null!; 30 | 31 | /// 32 | /// Scale all the cursor textures by this factor. 33 | /// 34 | public float CursorScale { get; set; } = 1f; 35 | 36 | /// 37 | /// Cursor to render in default state. 38 | /// 39 | public CursorProperties? CursorDefault { get; set; } 40 | 41 | /// 42 | /// Cursor to render while pointing on an interactable entity that is not locked or disabled. 43 | /// 44 | public CursorProperties? CursorInteractable { get; set; } 45 | 46 | /// 47 | /// Cursor to render while pointing on a disabled entity. 48 | /// 49 | public CursorProperties? CursorDisabled { get; set; } 50 | 51 | /// 52 | /// Cursor to render while pointing on a locked entity. 53 | /// 54 | public CursorProperties? CursorLocked { get; set; } 55 | 56 | /// 57 | /// How much space a row spacer unit takes, in pixels. 58 | /// This determine what the UI system defines as a default empty "row" size. 59 | /// 60 | public int RowSpaceHeight { get; set; } = 14; 61 | 62 | /// 63 | /// Lock entities to interactive state for at least this value in seconds, to make sure the 'interactive' state is properly displayed even for rapid clicks. 64 | /// 65 | public float TimeToLockInteractiveState { get; set; } 66 | 67 | /// 68 | /// Optional framed texture to render over focused entities. 69 | /// 70 | public FramedTexture? FocusedEntityOverlay { get; set; } 71 | 72 | /// 73 | /// General system icons. For example file and folder icons, used by files dialog. 74 | /// 75 | public Dictionary SystemIcons { get; set; } = new(); 76 | 77 | /// 78 | /// Default stylesheets to load for entities. 79 | /// Key = name of stylesheet to load (for example 'Panels' for 'uiSystem.DefaultStylesheets.Panels'. 80 | /// Value = path, relative to the folder containing this stylesheet, to load from. 81 | /// 82 | public Dictionary LoadDefaultStylesheets { get; set; } = null!; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Iguina/Defs/TextAlignment.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Defs 3 | { 4 | /// 5 | /// Text alignment properties. 6 | /// 7 | public enum TextAlignment 8 | { 9 | /// 10 | /// Left to right alignment. 11 | /// 12 | Left, 13 | 14 | /// 15 | /// Right to left alignment. 16 | /// 17 | Right, 18 | 19 | /// 20 | /// Center alignment. 21 | /// 22 | Center 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Iguina/Drivers/IFilesProvider.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Drivers 3 | { 4 | /// 5 | /// An interface to provide file reading access. 6 | /// By default it uses the simple built-in C# text file reader, but you can override this to get files content from other sources. 7 | /// 8 | public interface IFilesProvider 9 | { 10 | /// 11 | /// Read entire text file from a given path. 12 | /// 13 | /// File path. 14 | /// File content as string. 15 | public string ReadAllText(string path); 16 | } 17 | 18 | /// 19 | /// Built in files provider that simply read text files. 20 | /// 21 | public class DefaultFilesProvider : IFilesProvider 22 | { 23 | public string ReadAllText(string path) 24 | { 25 | return File.ReadAllText(path); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Iguina/Drivers/IInputProvider.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Drivers 5 | { 6 | /// 7 | /// An interface to provide input functionality for the GUI system. 8 | /// This is one of the drivers your application needs to provide. 9 | /// 10 | public interface IInputProvider 11 | { 12 | /// 13 | /// Get current mouse position. 14 | /// 15 | /// 16 | /// This doesn't have to actually be a mouse. For example, you can get input from gamepad and emulate mouse for gamepad controls. 17 | /// 18 | /// Mouse position. 19 | Point GetMousePosition(); 20 | 21 | /// 22 | /// Get the state of a mouse button. 23 | /// 24 | /// Mouse button to check. 25 | /// 26 | /// This doesn't have to actually be a mouse. For example, you can get input from gamepad and emulate mouse for gamepad controls. 27 | /// 28 | /// True if mouse button is currently down. 29 | bool IsMouseButtonDown(MouseButton btn); 30 | 31 | /// 32 | /// Get current mouse wheel value. 33 | /// -1 = mouse wheel scrolled up. 34 | /// 1 = mouse wheel scrolled down. 35 | /// 0 = no change to mouse wheel. 36 | /// 37 | /// Mouse wheel change value. 38 | int GetMouseWheelChange(); 39 | 40 | /// 41 | /// Get characters input from typing. 42 | /// 43 | /// Its the input provider responsibility to add 'Repeat Delay' and 'Repeating Rate'. Iguina will not limit or impose any delays on typing speed. 44 | /// Characters that were pressed this frame, as characters unicode values. 45 | int[] GetTextInput(); 46 | 47 | /// 48 | /// Get special text input commands from typing. 49 | /// 50 | /// Its the input provider responsibility to add 'Repeat Delay' and 'Repeat Rate'. Iguina will not limit or impose any delays on typing speed. 51 | /// Text commands that were pressed this frame. 52 | TextInputCommands[] GetTextInputCommands(); 53 | 54 | /// 55 | /// Optionally get keyboard-based interactions. 56 | /// This input is used on currently focused entity. 57 | /// 58 | /// Keyboard interaction command, or null if there are no keyboard interactions. 59 | KeyboardInteractions? GetKeyboardInteraction(); 60 | } 61 | 62 | /// 63 | /// Keyboard-based input commands. 64 | /// 65 | public enum KeyboardInteractions 66 | { 67 | /// 68 | /// Move up (typically on arrow up key press). 69 | /// 70 | MoveUp, 71 | 72 | /// 73 | /// Move down (typically on arrow down key press). 74 | /// 75 | MoveDown, 76 | 77 | /// 78 | /// Move left (typically on arrow left key press). 79 | /// 80 | MoveLeft, 81 | 82 | /// 83 | /// Move right (typically on arrow right key press). 84 | /// 85 | MoveRight, 86 | 87 | /// 88 | /// Select / click / toggle (typically on space / enter key press). 89 | /// 90 | Select 91 | } 92 | 93 | /// 94 | /// Special text input commands. 95 | /// 96 | public enum TextInputCommands 97 | { 98 | MoveCaretLeft, 99 | MoveCaretRight, 100 | MoveCaretUp, 101 | MoveCaretDown, 102 | Backspace, 103 | Delete, 104 | BreakLine, 105 | MoveCaretEnd, 106 | MoveCaretStart, 107 | MoveCaretEndOfLine, 108 | MoveCaretStartOfLine, 109 | } 110 | 111 | /// 112 | /// Mouse buttons. 113 | /// 114 | public enum MouseButton 115 | { 116 | Left, 117 | Wheel, 118 | Right 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Iguina/Drivers/IRenderer.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Drivers 5 | { 6 | /// 7 | /// An interface to provide rendering functionality for the GUI system. 8 | /// This is one of the drivers your application needs to provide. 9 | /// 10 | public interface IRenderer 11 | { 12 | /// 13 | /// Get screen bounds, in pixels. 14 | /// 15 | /// Current screen bounds. 16 | Rectangle GetScreenBounds(); 17 | 18 | /// 19 | /// Draw a texture. 20 | /// 21 | /// Effect to use, or null for default. 22 | /// Texture id to draw. 23 | /// Destination rectangle. 24 | /// Source rectangle. 25 | /// Rendering color. 26 | void DrawTexture(string? effectIdentifier, string textureId, Rectangle destRect, Rectangle sourceRect, Color color); 27 | 28 | /// 29 | /// Measure text size. 30 | /// 31 | /// Text to measure. 32 | /// Font identifier. 33 | /// Font size. 34 | /// Font spacing factor. 1f = default font spacing. 35 | /// Text size, in pixels. 36 | Point MeasureText(string text, string? fontId, int fontSize, float spacing); 37 | 38 | /// 39 | /// Get line height for a font id and size. 40 | /// 41 | /// Font identifier. 42 | /// Font size. 43 | /// Text line height. 44 | int GetTextLineHeight(string? fontId, int fontSize); 45 | 46 | /// 47 | /// Draw text. 48 | /// 49 | /// Effect to use, or null for default. 50 | /// Text to render. 51 | /// Font identifier to use, or null for default font. 52 | /// Font size. 53 | /// Text position. 54 | /// Text fill color. 55 | /// Text outline color. 56 | /// Outline width, in pixels. 57 | /// Font spacing factor. 1f = default font spacing. 58 | void DrawText(string? effectIdentifier, string text, string? fontId, int fontSize, Point position, Color fillColor, Color outlineColor, int outlineWidth, float spacing); 59 | 60 | /// 61 | /// Draw a filled rectangle. 62 | /// 63 | /// Rectangle region. 64 | /// Rectangle color. 65 | void DrawRectangle(Rectangle rectangle, Color color); 66 | 67 | /// 68 | /// Set a rectangle region we can render on. 69 | /// Any rendering that exceed this region, would be culled out. 70 | /// 71 | /// Visible region. Only pixels within this rectangle will be rendered. 72 | void SetScissorRegion(Rectangle region); 73 | 74 | /// 75 | /// Get current scissor region, if set. 76 | /// 77 | /// Current scissor region or null if not set. 78 | Rectangle? GetScissorRegion(); 79 | 80 | /// 81 | /// Clear scissor region, if set. 82 | /// 83 | void ClearScissorRegion(); 84 | 85 | /// 86 | /// Get pixel color from texture and offset. 87 | /// 88 | /// Texture id to get pixel color from. 89 | /// Source position in texture. 90 | /// Pixel color in source texture. 91 | Color GetPixelFromTexture(string textureId, Point sourcePosition); 92 | 93 | /// 94 | /// Locate an offset in a given texture and source region that has a specific color. 95 | /// 96 | /// Texture id to find pixel color from. 97 | /// Source region to search in. 98 | /// Color to find. 99 | /// If true and exact color is not found, will return nearest color offset instead. 100 | /// Pixel offset, from source rect top-left corner. If color is not found, will return null. 101 | Point? FindPixelOffsetInTexture(string textureId, Rectangle sourceRect, Color color, bool returnNearestColor); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Iguina/Entities/Button.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | namespace Iguina.Entities 4 | { 5 | /// 6 | /// Button entity type. 7 | /// 8 | public class Button : CheckedEntity 9 | { 10 | /// 11 | /// Button label. 12 | /// 13 | public Paragraph Paragraph { get; private set; } 14 | 15 | /// 16 | internal override bool Interactable => true; 17 | 18 | /// 19 | /// Create the button. 20 | /// 21 | /// Parent UI system. 22 | /// Button stylesheet. 23 | /// Button text. 24 | public Button(UISystem system, StyleSheet? stylesheet, string text = "New Button") : base(system, stylesheet) 25 | { 26 | // create the button paragraph 27 | Paragraph = new Paragraph(system, stylesheet, text); 28 | Paragraph.DrawFillTexture = false; 29 | AddChildInternal(Paragraph); 30 | Paragraph.CopyStateFrom = this; 31 | OverflowMode = OverflowMode.HideOverflow; 32 | } 33 | 34 | /// 35 | /// Create the button with default stylesheets. 36 | /// 37 | /// Parent UI system. 38 | /// Button text. 39 | public Button(UISystem system, string text = "New Button") : this(system, system.DefaultStylesheets.Buttons, text) 40 | { 41 | } 42 | 43 | /// 44 | protected override MeasureVector GetDefaultEntityTypeSize() 45 | { 46 | var ret = new MeasureVector(); 47 | ret.X.SetPercents(100f); 48 | ret.Y.SetPixels(54); 49 | return ret; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Iguina/Entities/Checkbox.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | namespace Iguina.Entities 4 | { 5 | /// 6 | /// Checkbox entity type. 7 | /// 8 | public class Checkbox : CheckedEntity 9 | { 10 | /// 11 | /// Checkbox label. 12 | /// 13 | public Paragraph Paragraph { get; private set; } 14 | 15 | /// 16 | internal override bool Interactable => true; 17 | 18 | /// 19 | /// Create the checkbox. 20 | /// 21 | /// Parent UI system. 22 | /// Checkbox stylesheet. 23 | /// Checkbox text. 24 | public Checkbox(UISystem system, StyleSheet? stylesheet, string text = "New Checkbox") : base(system, stylesheet) 25 | { 26 | // create the checkbox paragraph 27 | Paragraph = new Paragraph(system, stylesheet, text); 28 | Paragraph.DrawFillTexture = false; 29 | AddChildInternal(Paragraph); 30 | Paragraph.CopyStateFrom = this; 31 | 32 | // make checkable 33 | ToggleCheckOnClick = true; 34 | CanClickToUncheck = true; 35 | 36 | } 37 | 38 | /// 39 | /// Create the checkbox with default stylesheets. 40 | /// 41 | /// Parent UI system. 42 | /// Checkbox text. 43 | public Checkbox(UISystem system, string text = "New Checkbox") : this(system, system.DefaultStylesheets.CheckBoxes, text) 44 | { 45 | } 46 | 47 | /// 48 | protected override MeasureVector GetDefaultEntityTypeSize() 49 | { 50 | var ret = new MeasureVector(); 51 | ret.X.SetPercents(100f); 52 | ret.Y.SetPixels(54); 53 | return ret; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Iguina/Entities/CheckedEntity.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// Entity type that have 'Checked' property that can be checked / unchecked. 8 | /// 9 | public abstract class CheckedEntity : Entity 10 | { 11 | /// 12 | /// Set / get if this entity is in 'checked' state. 13 | /// Useful for buttons anc checkboxes, but can be set for any type of entity. 14 | /// 15 | public virtual bool Checked 16 | { 17 | get => _isChecked; 18 | set 19 | { 20 | // change checked state 21 | if (value != _isChecked) 22 | { 23 | // uncheck siblings 24 | if (ExclusiveSelection) 25 | { 26 | Parent?.IterateChildren((Entity entity) => 27 | { 28 | var siblingAsCheckable = entity as CheckedEntity; 29 | if ((siblingAsCheckable != null) && (siblingAsCheckable != this) && (siblingAsCheckable.ExclusiveSelection)) 30 | { 31 | siblingAsCheckable.Checked = false; 32 | } 33 | return true; 34 | }); 35 | } 36 | 37 | // set value 38 | _isChecked = value; 39 | 40 | // invoke value change callbacks 41 | Events.OnValueChanged?.Invoke(this); 42 | UISystem.Events.OnValueChanged?.Invoke(this); 43 | 44 | // invoke checked / unchecked callbacks 45 | if (_isChecked) 46 | { 47 | Events.OnChecked?.Invoke(this); 48 | UISystem.Events.OnChecked?.Invoke(this); 49 | } 50 | else 51 | { 52 | Events.OnUnchecked?.Invoke(this); 53 | UISystem.Events.OnUnchecked?.Invoke(this); 54 | } 55 | } 56 | } 57 | } 58 | 59 | /// 60 | /// If true, this entity will check / uncheck itself when clicked on. 61 | /// 62 | public bool ToggleCheckOnClick = false; 63 | 64 | /// 65 | /// If true, when this entity is checked, all its direct siblings with this property will be automatically unchecked. 66 | /// This is used for things like radio button where only one option can be checked at any given moment. 67 | /// 68 | public bool ExclusiveSelection = false; 69 | 70 | /// 71 | /// If false, it will be impossible to uncheck this entity once its checked by clicking on it. 72 | /// However, if the 'ExclusiveSelection' is set, you can still uncheck it by checking another sibling. 73 | /// 74 | public bool CanClickToUncheck = true; 75 | 76 | /// 77 | public CheckedEntity(UISystem system, StyleSheet? stylesheet) : base(system, stylesheet) 78 | { 79 | } 80 | 81 | /// 82 | /// Toggle this entity checked state. 83 | /// 84 | public void ToggleCheckedState() 85 | { 86 | // disable unchecking 87 | if (!CanClickToUncheck && Checked) 88 | { 89 | return; 90 | } 91 | 92 | // toggle checked mode 93 | Checked = !Checked; 94 | } 95 | 96 | /// 97 | internal override void DoInteractions(InputState inputState) 98 | { 99 | // call base class to trigger events 100 | base.DoInteractions(inputState); 101 | 102 | // check / uncheck 103 | if (ToggleCheckOnClick) 104 | { 105 | if (inputState.LeftMouseReleasedNow) 106 | { 107 | ToggleCheckedState(); 108 | } 109 | } 110 | } 111 | 112 | internal override void DoFocusedEntityInteractions(InputState inputState) 113 | { 114 | // call base class to trigger events 115 | base.DoFocusedEntityInteractions(inputState); 116 | 117 | // implement click via keyboard 118 | if (ToggleCheckOnClick) 119 | { 120 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.Select) 121 | { 122 | ToggleCheckedState(); 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Iguina/Entities/ColorPicker.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using System.Numerics; 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// Color picker lets the user pick a color from a 2d source texture, with X and Y axes. 8 | /// This is useful for color pickers that have a combination of hue and brightness. 9 | /// 10 | public class ColorPicker : Entity, IColorPicker 11 | { 12 | /// 13 | /// The entity used as the color picker handle. 14 | /// 15 | public Entity Handle { get; private set; } 16 | 17 | // last handle offset 18 | Point _lastHandleOffset = new Point(-1, -1); 19 | 20 | /// 21 | internal override bool Interactable => true; 22 | 23 | /// 24 | /// Get / set color picker value, as a color extracted from the source texture. 25 | /// 26 | public Color ColorValue 27 | { 28 | get => _colorValue; 29 | set 30 | { 31 | _colorValue = value; 32 | var srcTexture = SourceTextureData; 33 | var offset = UISystem.Renderer.FindPixelOffsetInTexture(srcTexture!.TextureId ?? UISystem.SystemStyleSheet.DefaultTexture, srcTexture.SourceRect, value, false); 34 | if (offset.HasValue) 35 | { 36 | SetHandleOffsetFromSource(offset.Value); 37 | } 38 | } 39 | } 40 | 41 | /// 42 | public void SetColorValueApproximate(Color value) 43 | { 44 | _colorValue = value; 45 | var srcTexture = SourceTextureData; 46 | var offset = UISystem.Renderer.FindPixelOffsetInTexture(srcTexture!.TextureId ?? UISystem.SystemStyleSheet.DefaultTexture, srcTexture.SourceRect, value, true); 47 | if (offset.HasValue) 48 | { 49 | SetHandleOffsetFromSource(offset.Value); 50 | } 51 | } 52 | 53 | // current color value 54 | Color _colorValue; 55 | 56 | // offset in source texture, from source rectangle top-left corner 57 | Point _offsetInSource; 58 | 59 | /// 60 | /// Get source stretch texture. 61 | /// 62 | StretchedTexture? SourceTextureData => StyleSheet.GetProperty("FillTextureStretched", State, null, OverrideStyles); 63 | 64 | /// 65 | /// Create the color picker. 66 | /// 67 | /// Parent UI system. 68 | /// Color picker stylesheet. 69 | /// Color picker handle stylesheet. 70 | public ColorPicker(UISystem system, StyleSheet? stylesheet, StyleSheet? handleStylesheet) : base(system, stylesheet) 71 | { 72 | // create handle 73 | Handle = new Entity(system, handleStylesheet); 74 | Handle.CopyStateFrom = this; 75 | Handle.Anchor = Anchor.TopLeft; 76 | Handle.TransferInteractionsTo = this; 77 | AddChildInternal(Handle); 78 | Handle.IgnoreScrollOffset = true; 79 | } 80 | 81 | /// 82 | /// Create the color picker with default stylesheets. 83 | /// 84 | /// Parent UI system. 85 | public ColorPicker(UISystem system) : 86 | this(system, 87 | system.DefaultStylesheets.ColorPickers, 88 | system.DefaultStylesheets.ColorPickersHandle) 89 | { 90 | } 91 | 92 | /// 93 | protected override void Update(float dt) 94 | { 95 | base.Update(dt); 96 | 97 | if ((_lastHandleOffset.X != _offsetInSource.X) || (_lastHandleOffset.Y != _offsetInSource.Y)) 98 | { 99 | _lastHandleOffset = _offsetInSource; 100 | UpdateValueFromHandle(); 101 | Events.OnValueChanged?.Invoke(this); 102 | } 103 | } 104 | 105 | /// 106 | /// Get factor from destination size to source size. 107 | /// 108 | Vector2 GetDestToSourceFactor() 109 | { 110 | var srcTexture = SourceTextureData; 111 | var srcWidth = (srcTexture?.SourceRect.Width ?? 1f); 112 | var srcHeight = (srcTexture?.SourceRect.Height ?? 1f); 113 | float factorX = srcWidth / (float)Math.Max(1, LastBoundingRect.Width); 114 | float factorY = srcHeight / (float)Math.Max(1, LastBoundingRect.Height); 115 | return new Vector2(factorX, factorY); 116 | } 117 | 118 | /// 119 | /// Set the color picker handle offset. 120 | /// 121 | /// Color picker offset, in pixels, from top-left corner. 122 | public void SetHandleOffset(Point offset) 123 | { 124 | var factor = GetDestToSourceFactor(); 125 | _offsetInSource.X = (int)Math.Ceiling((float)offset.X * factor.X); 126 | _offsetInSource.Y = (int)Math.Ceiling((float)offset.Y * factor.Y); 127 | } 128 | 129 | /// 130 | /// Set the color picker handle offset, with offset representing pixel offset in texture from the picker source rectangle top left corner. 131 | /// 132 | /// Color picker offset, in pixels, from top-left corner of the texture source rectangle. 133 | public void SetHandleOffsetFromSource(Point offset) 134 | { 135 | _offsetInSource = offset; 136 | } 137 | 138 | /// 139 | /// Update color value from handle offset. 140 | /// 141 | void UpdateValueFromHandle() 142 | { 143 | var srcTexture = SourceTextureData; 144 | _colorValue = UISystem.Renderer.GetPixelFromTexture(srcTexture!.TextureId ?? UISystem.SystemStyleSheet.DefaultTexture ?? string.Empty, 145 | new Point( 146 | srcTexture.SourceRect.X + _offsetInSource.X, 147 | srcTexture.SourceRect.Y + _offsetInSource.Y) 148 | ); 149 | } 150 | 151 | /// 152 | internal override void DoInteractions(InputState inputState) 153 | { 154 | // do base interactions 155 | base.DoInteractions(inputState); 156 | 157 | // select value via mouse 158 | if (inputState.LeftMouseDown) 159 | { 160 | SetHandleOffset(new Point(inputState.MousePosition.X - LastBoundingRect.X, inputState.MousePosition.Y - LastBoundingRect.Y)); 161 | } 162 | } 163 | 164 | /// 165 | protected override DrawMethodResult Draw(DrawMethodResult parentDrawResult, DrawMethodResult? siblingDrawResult, bool dryRun) 166 | { 167 | // update handle offset 168 | var factor = GetDestToSourceFactor(); 169 | Handle.Offset.X.Value = MathF.Ceiling(_offsetInSource.X / factor.X - Handle.LastBoundingRect.Width / 2); 170 | Handle.Offset.Y.Value = MathF.Ceiling(_offsetInSource.Y / factor.Y - Handle.LastBoundingRect.Height / 2); 171 | return base.Draw(parentDrawResult, siblingDrawResult, dryRun); 172 | } 173 | 174 | /// 175 | internal override void DoFocusedEntityInteractions(InputState inputState) 176 | { 177 | // call base class to trigger events 178 | base.DoFocusedEntityInteractions(inputState); 179 | 180 | // move value via keyboard - horizontal 181 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveLeft) 182 | { 183 | if (_offsetInSource.X > 0) 184 | { 185 | _offsetInSource.X--; 186 | UpdateValueFromHandle(); 187 | } 188 | } 189 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveRight) 190 | { 191 | if (_offsetInSource.X < SourceTextureData?.SourceRect.Width) 192 | { 193 | _offsetInSource.X++; 194 | UpdateValueFromHandle(); 195 | } 196 | } 197 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveUp) 198 | { 199 | if (_offsetInSource.Y > 0) 200 | { 201 | _offsetInSource.Y--; 202 | UpdateValueFromHandle(); 203 | } 204 | } 205 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveDown) 206 | { 207 | if (_offsetInSource.Y < SourceTextureData?.SourceRect.Height) 208 | { 209 | _offsetInSource.Y++; 210 | UpdateValueFromHandle(); 211 | } 212 | } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Iguina/Entities/ColorSlider.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | namespace Iguina.Entities 4 | { 5 | /// 6 | /// Color slider is a color picker entity that is based on slider, where the user can pick a color from a range. 7 | /// This picker is useful for when the user can pick hue without choosing brightness or saturation. 8 | /// 9 | public class ColorSlider : Slider, IColorPicker 10 | { 11 | /// 12 | /// Get / set slider color value, based on the default state source texture. 13 | /// 14 | public Color ColorValue 15 | { 16 | get 17 | { 18 | var srcRect = SourceRectangle; 19 | if (Orientation == Orientation.Horizontal) 20 | { 21 | return UISystem.Renderer.GetPixelFromTexture(SourceTextureId, new Point(srcRect.Left + (int)Math.Round(ValuePercent * (srcRect.Width - 0.1f)), srcRect.Top + srcRect.Height / 2)); 22 | } 23 | else 24 | { 25 | return UISystem.Renderer.GetPixelFromTexture(SourceTextureId, new Point(srcRect.Left + srcRect.Width / 2, srcRect.Top + (int)Math.Round(ValuePercent * (srcRect.Height - 0.1f)))); 26 | } 27 | } 28 | set 29 | { 30 | var src = SourceRectangle; 31 | src.Y += src.Height / 2; 32 | src.Height = 1; 33 | var offset = UISystem.Renderer.FindPixelOffsetInTexture(SourceTextureId, SourceRectangle, value, false); 34 | if (offset.HasValue) 35 | { 36 | if (Orientation == Orientation.Horizontal) 37 | { 38 | ValueSafe = offset.Value.X; 39 | } 40 | else 41 | { 42 | ValueSafe = offset.Value.Y; 43 | } 44 | } 45 | } 46 | } 47 | 48 | /// 49 | /// Create the colors slider. 50 | /// 51 | /// Parent UI system. 52 | /// Slider stylesheet. 53 | /// Slider handle stylesheet. 54 | /// Slider orientation. 55 | public ColorSlider(UISystem system, StyleSheet? stylesheet, StyleSheet? handleStylesheet, Orientation orientation = Orientation.Horizontal) : base(system, stylesheet, handleStylesheet, orientation) 56 | { 57 | AutoSetRange = true; 58 | SetAutoRange(); 59 | } 60 | 61 | /// 62 | /// Create the color slider with default stylesheets. 63 | /// 64 | /// Parent UI system. 65 | /// Slider orientation. 66 | public ColorSlider(UISystem system, Orientation orientation = Orientation.Horizontal) : 67 | this(system, 68 | (orientation == Orientation.Horizontal) ? (system.DefaultStylesheets.HorizontalColorSliders ?? system.DefaultStylesheets.HorizontalSliders) : (system.DefaultStylesheets.VerticalColorSliders ?? system.DefaultStylesheets.VerticalSliders), 69 | (orientation == Orientation.Horizontal) ? (system.DefaultStylesheets.HorizontalColorSlidersHandle ?? system.DefaultStylesheets.HorizontalSlidersHandle) : (system.DefaultStylesheets.VerticalColorSlidersHandle ?? system.DefaultStylesheets.VerticalSlidersHandle), 70 | orientation) 71 | { 72 | } 73 | 74 | /// 75 | public void SetColorValueApproximate(Color value) 76 | { 77 | var src = SourceRectangle; 78 | src.Y += src.Height / 2; 79 | src.Height = 1; 80 | var offset = UISystem.Renderer.FindPixelOffsetInTexture(SourceTextureId, SourceRectangle, value, true); 81 | if (offset.HasValue) 82 | { 83 | if (Orientation == Orientation.Horizontal) 84 | { 85 | ValueSafe = offset.Value.X; 86 | } 87 | else 88 | { 89 | ValueSafe = offset.Value.Y; 90 | } 91 | } 92 | } 93 | 94 | /// 95 | /// Get source stretch texture. 96 | /// 97 | StretchedTexture? SourceTextureData => StyleSheet.GetProperty("FillTextureStretched", State, null, OverrideStyles); 98 | 99 | /// 100 | /// Get slider source rectangle. 101 | /// 102 | Rectangle SourceRectangle => SourceTextureData?.SourceRect ?? Rectangle.Empty; 103 | 104 | /// 105 | /// Get slider source texture. 106 | /// 107 | string SourceTextureId => SourceTextureData?.TextureId ?? UISystem.SystemStyleSheet.DefaultTexture ?? string.Empty; 108 | 109 | /// 110 | protected override void SetAutoRange() 111 | { 112 | MinValue = 0; 113 | if (Orientation == Orientation.Horizontal) 114 | { 115 | MaxValue = Math.Max(SourceTextureData?.SourceRect.Width ?? LastBoundingRect.Width, 10); 116 | } 117 | else 118 | { 119 | MaxValue = Math.Max(SourceTextureData?.SourceRect.Height ?? LastBoundingRect.Height, 10); 120 | } 121 | StepsCount = (uint)MaxValue; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Iguina/Entities/DropDown.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// A list of items users can select items from, that collapses while not being interacted with. 8 | /// 9 | public class DropDown : ListBox 10 | { 11 | /// 12 | /// Is the dropdown currently opened? 13 | /// 14 | public bool IsOpened { get; private set; } 15 | 16 | /// 17 | protected override bool ShowListScrollbar => base.ShowListScrollbar && IsOpened; 18 | 19 | /// 20 | /// Text to show when no value is selected and dropdown is collapsed. 21 | /// 22 | public string? DefaultSelectedText = null; 23 | 24 | /// 25 | internal override bool TopMostInteractions => IsOpened; 26 | 27 | /// 28 | /// If defined, this will be the text to display when the dropdown is collapsed, regardless of the currently selected item or default text. 29 | /// 30 | public string? OverrideSelectedText = null; 31 | 32 | /// 33 | /// Styles to override stylesheet defaults, regardless of entity state, for the paragraph showing the selected value in closed state. 34 | /// 35 | public StyleSheetState OverrideClosedStateTextStyles 36 | { 37 | get => _selectedValueParagraph.OverrideStyles; 38 | set => _selectedValueParagraph.OverrideStyles = value; 39 | } 40 | 41 | // panel for selected value text 42 | Panel _selectedValuePanel; 43 | Paragraph _selectedValueParagraph; 44 | 45 | // icon entity, if stylesheet is defined for it 46 | Entity _icon = null!; 47 | 48 | /// 49 | /// Create the drop down. 50 | /// 51 | /// Parent UI system. 52 | /// Drop down panel stylesheet. 53 | /// Drop down box items stylesheet. If not set, will use the same as base stylesheet. 54 | /// Stylesheet for an arrow icon to add to the dropdown to reflect its state. If null, will not add this entity. 55 | public DropDown(UISystem system, StyleSheet? stylesheet, StyleSheet? itemsStylesheet = null, StyleSheet? arrowIconStylesheet = null) : base(system, stylesheet, itemsStylesheet) 56 | { 57 | // set as auto-height by default 58 | AutoHeight = true; 59 | 60 | // create panel with selected value paragraph 61 | _selectedValuePanel = new Panel(system, stylesheet); 62 | _selectedValueParagraph = _selectedValuePanel.AddChild(new Paragraph(system, itemsStylesheet ?? stylesheet, string.Empty, true)); 63 | _selectedValuePanel.Size.X.SetPercents(100f); 64 | _selectedValuePanel.Size.Y.SetPixels(GetClosedStateHeight()); 65 | _selectedValuePanel.IgnoreScrollOffset = true; 66 | _selectedValuePanel._overrideInteractableState = true; 67 | _selectedValuePanel.PassFocusTo = this; 68 | var padding = GetPadding(); 69 | _selectedValuePanel.Offset.X.Value = -padding.Left; 70 | _selectedValuePanel.Offset.Y.Value = -padding.Top; 71 | _selectedValuePanel.OverrideStyles.ExtraSize = new Sides() { Right = padding.Left + padding.Right }; 72 | _selectedValuePanel.ExtraMarginForInteractions = new Sides(padding.Left, padding.Right, padding.Top, 0); 73 | AddChildInternal(_selectedValuePanel); 74 | 75 | // create dropdown icon 76 | if (arrowIconStylesheet != null) 77 | { 78 | _icon = new Entity(UISystem, arrowIconStylesheet); 79 | _icon.CopyStateFrom = this; 80 | _icon.IgnoreInteractions = true; 81 | _icon.IgnoreScrollOffset = true; 82 | AddChildInternal(_icon); 83 | } 84 | 85 | // clicking on selected value panel will open / close dropdown 86 | _selectedValuePanel.Events.OnClick += (Entity entity) => 87 | { 88 | ToggleList(); 89 | }; 90 | } 91 | 92 | /// 93 | /// Show / hide arrow icon. 94 | /// 95 | /// Should we show or hide the dropdown arrow icon. 96 | public void ShowArrowIcon(bool show) 97 | { 98 | if (_icon != null) 99 | { 100 | _icon.Visible = show; 101 | } 102 | } 103 | 104 | /// 105 | /// Create the drop down with default stylesheets. 106 | /// 107 | /// Parent UI system. 108 | public DropDown(UISystem system) : this(system, 109 | system.DefaultStylesheets.DropDownPanels ?? system.DefaultStylesheets.ListPanels ?? system.DefaultStylesheets.Panels, 110 | system.DefaultStylesheets.DropDownItems ?? system.DefaultStylesheets.ListItems ?? system.DefaultStylesheets.Paragraphs, 111 | system.DefaultStylesheets.DropDownIcon) 112 | { 113 | } 114 | 115 | /// 116 | protected override void SetAutoSizes(int maxWidth, int maxHeight) 117 | { 118 | // set auto width 119 | if (AutoWidth) 120 | { 121 | var width = maxWidth; 122 | if (AutoWidthMaxSize.HasValue && width > AutoWidthMaxSize.Value) 123 | { 124 | width = AutoWidthMaxSize.Value; 125 | } 126 | Size.X.SetPixels(width); 127 | } 128 | 129 | // set auto height 130 | if (AutoHeight) 131 | { 132 | var extraSize = _selectedValuePanel.GetExtraSize(); 133 | var selectedPanelHeight = _selectedValuePanel.LastBoundingRect.Height + _selectedValuePanel.GetMarginAfter().Y + extraSize.Bottom + extraSize.Top; 134 | var height = (int)(ItemHeight * ((float)ItemsCount + 0.5f) + selectedPanelHeight); 135 | if (AutoHeightMaxSize.HasValue && height > AutoHeightMaxSize.Value) 136 | { 137 | height = AutoHeightMaxSize.Value; 138 | } 139 | Size.Y.SetPixels(height); 140 | } 141 | } 142 | 143 | 144 | /// 145 | /// Get the dropdown height, in pixels, when its closed. 146 | /// 147 | /// Drop down height in pixels when closed. 148 | public int GetClosedStateHeight(bool includePadding = true, bool includeExtraSize = true) 149 | { 150 | var padding = includePadding ? GetPadding() : Sides.Zero; 151 | var extra = includeExtraSize ? GetExtraSize() : Sides.Zero; 152 | return ItemHeight + padding.Top + padding.Bottom + extra.Top + extra.Bottom; 153 | } 154 | 155 | /// 156 | /// Set bounding rectangles size to its closed state. 157 | /// 158 | private void SetSizeToClosedState(ref Rectangle boundingRect, ref Rectangle internalBoundingRect) 159 | { 160 | internalBoundingRect.Height = GetClosedStateHeight(); 161 | boundingRect.Height = internalBoundingRect.Height; 162 | } 163 | 164 | /// 165 | protected override int GetExtraParagraphsCount() 166 | { 167 | return -2; // -2 to compensate top panel that shows selected value 168 | } 169 | 170 | /// 171 | public override void SetVisibleItemsCount(int items) 172 | { 173 | AutoHeight = false; 174 | Size.Y.SetPixels(ItemHeight * (items + 2)); // +2 to compensate top panel that shows selected value 175 | } 176 | 177 | /// 178 | protected override void DrawEntityType(ref Rectangle boundingRect, ref Rectangle internalBoundingRect, DrawMethodResult parentDrawResult, DrawMethodResult? siblingDrawResult) 179 | { 180 | // special - if we are rendering in open mode, top most 181 | if (_isCurrentlyDrawingOpenedListTopMost) 182 | { 183 | // draw open list 184 | base.DrawEntityType(ref boundingRect, ref internalBoundingRect, parentDrawResult, siblingDrawResult); 185 | 186 | // this part makes sure the top panel border is not hidden 187 | Rectangle rect = boundingRect; 188 | rect.Height = GetClosedStateHeight(); 189 | DrawFillTextures(rect); 190 | return; 191 | } 192 | 193 | // closed? resize to close state BEFORE rendering 194 | if (!IsOpened) 195 | { 196 | SetSizeToClosedState(ref boundingRect, ref internalBoundingRect); 197 | } 198 | 199 | // dropdown is opened - render as list top-most 200 | if (IsOpened) 201 | { 202 | // move the scrollbar under the selected value box 203 | { 204 | if (VerticalScrollbar != null) 205 | { 206 | var extra = GetExtraSize(); 207 | var scrollbarOffset = (int)((GetClosedStateHeight() - extra.Bottom) * 0.85f); 208 | VerticalScrollbar.Offset.Y.Value = scrollbarOffset; 209 | VerticalScrollbar.OverrideStyles.ExtraSize = new Sides(0, 0, 0, -scrollbarOffset); 210 | } 211 | } 212 | 213 | // draw opened list in top-most mode at the end of frame 214 | DrawMethodResult parentResults = parentDrawResult; 215 | DrawMethodResult? siblingResults = siblingDrawResult; 216 | UISystem.RunAfterDrawingEntities(() => 217 | { 218 | _isCurrentlyDrawingOpenedListTopMost = true; 219 | _DoDraw(parentResults, siblingResults, false); 220 | _isCurrentlyDrawingOpenedListTopMost = false; 221 | }); 222 | } 223 | // dropdown is closed - render normally in close state 224 | else 225 | { 226 | base.DrawEntityType(ref boundingRect, ref internalBoundingRect, parentDrawResult, siblingDrawResult); 227 | } 228 | 229 | // opened? resize to close state AFTER rendering 230 | // this way we render the entire open list, but without pushing down auto anchors below (ie the entity only takes the size of its close state when positioning). 231 | if (IsOpened) 232 | { 233 | SetSizeToClosedState(ref boundingRect, ref internalBoundingRect); 234 | } 235 | } 236 | bool _isCurrentlyDrawingOpenedListTopMost = false; 237 | 238 | /// 239 | /// Close the dropdown list. 240 | /// 241 | public void CloseList() 242 | { 243 | IsOpened = false; 244 | } 245 | 246 | /// 247 | /// Open the dropdown list. 248 | /// 249 | public void OpenList() 250 | { 251 | IsOpened = true; 252 | } 253 | 254 | /// 255 | /// Toggle dropdown list state. 256 | /// 257 | public void ToggleList() 258 | { 259 | if (IsOpened) 260 | { 261 | CloseList(); 262 | } 263 | else 264 | { 265 | OpenList(); 266 | } 267 | } 268 | 269 | /// 270 | protected override void OnItemClicked(Entity entity) 271 | { 272 | // if closed, open the list 273 | if (!IsOpened) 274 | { 275 | OpenList(); 276 | } 277 | // if opened, call default action and close the list 278 | else 279 | { 280 | base.OnItemClicked(entity); 281 | CloseList(); 282 | } 283 | } 284 | 285 | /// 286 | protected override void SetParagraphs(int scrollOffset, int startIndex = 0) 287 | { 288 | if (IsOpened) 289 | { 290 | if (_paragraphs.Count > 0) 291 | { 292 | _paragraphs[0].Offset.Y.Value = ItemHeight / 2; 293 | } 294 | base.SetParagraphs(scrollOffset, startIndex); 295 | } 296 | else 297 | { 298 | foreach (var p in _paragraphs) 299 | { 300 | p.Visible = false; 301 | } 302 | } 303 | } 304 | 305 | /// 306 | internal override void PostUpdate(InputState inputState) 307 | { 308 | // update selected value text 309 | _selectedValueParagraph.Text = OverrideSelectedText ?? SelectedTextWithIcon ?? SelectedValue ?? DefaultSelectedText ?? string.Empty; 310 | _selectedValueParagraph.UseEmptyValueTextColor = (SelectedValue == null); 311 | 312 | // set icon state 313 | if (_icon != null) 314 | { 315 | _icon.LockedState = IsOpened ? EntityState.Interacted : null; 316 | } 317 | 318 | // if opened and click outside, close the list 319 | if (IsOpened && inputState.LeftMousePressedNow) 320 | { 321 | // clicked on closed state box? skip 322 | if (_selectedValuePanel.IsPointedOn(inputState.MousePosition)) 323 | { 324 | return; 325 | } 326 | 327 | // if got here and point outside the list, close. 328 | if (!IsPointedOn(inputState.MousePosition)) 329 | { 330 | CloseList(); 331 | } 332 | } 333 | } 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /Iguina/Entities/HorizontalLine.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// A graphical horizontal line that separates between entities. 8 | /// 9 | public class HorizontalLine : Entity 10 | { 11 | /// 12 | /// Create the horizontal line. 13 | /// 14 | /// Parent UI system. 15 | /// Horizontal line stylesheet. 16 | public HorizontalLine(UISystem system, StyleSheet? stylesheet) : base(system, stylesheet) 17 | { 18 | IgnoreInteractions = true; 19 | } 20 | 21 | /// 22 | /// Create the horizontal line with default stylesheets. 23 | /// 24 | /// Parent UI system. 25 | public HorizontalLine(UISystem system) : this(system, system.DefaultStylesheets.HorizontalLines) 26 | { 27 | } 28 | 29 | /// 30 | protected override Anchor GetDefaultEntityTypeAnchor() 31 | { 32 | return Anchor.AutoCenter; 33 | } 34 | 35 | /// 36 | protected override MeasureVector GetDefaultEntityTypeSize() 37 | { 38 | var ret = new MeasureVector(); 39 | ret.X.SetPercents(100f); 40 | ret.Y.SetPixels(8); 41 | return ret; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Iguina/Entities/IColorPicker.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | namespace Iguina.Entities 4 | { 5 | /// 6 | /// Interface for an entity that is used to pick color. 7 | /// 8 | public interface IColorPicker 9 | { 10 | /// 11 | /// Get / set the picker color value. 12 | /// 13 | Color ColorValue { get; set; } 14 | 15 | /// 16 | /// Set the color value of this picker from a given color. 17 | /// If color is not found in source texture, it will set to the nearest found value. 18 | /// 19 | /// Color value to set to. 20 | void SetColorValueApproximate(Color value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Iguina/Entities/Label.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// A label text entity. 8 | /// Same as a paragraph, but with different defaults values and stylesheet. 9 | /// 10 | public class Label : Paragraph 11 | { 12 | /// 13 | /// Create the label. 14 | /// 15 | /// Parent UI system. 16 | /// Label stylesheet. 17 | /// Label text. 18 | /// If true, this label will ignore user interactions. 19 | public Label(UISystem system, StyleSheet? stylesheet, string text = "New Label", bool ignoreInteractions = true) : base(system, stylesheet, text, ignoreInteractions) 20 | { 21 | } 22 | 23 | /// 24 | /// Create the label with default stylesheets. 25 | /// 26 | /// Parent UI system. 27 | /// Label text. 28 | /// If true, this label will ignore user interactions. 29 | public Label(UISystem system, string text = "New Label", bool ignoreInteractions = true) : this(system, system.DefaultStylesheets.Labels, text, ignoreInteractions) 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Iguina/Entities/Panel.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Utils; 3 | 4 | 5 | namespace Iguina.Entities 6 | { 7 | /// 8 | /// A panel is a container for other entities. 9 | /// 10 | public class Panel : Entity 11 | { 12 | // so that scrollbars will work 13 | /// 14 | protected override bool WalkInternalChildren => true; 15 | 16 | /// 17 | /// Vertical scrollbar entity, if set. 18 | /// 19 | public Slider? VerticalScrollbar { get; private set; } 20 | 21 | /// 22 | protected override bool HaveScrollbars => VerticalScrollbar != null; 23 | 24 | 25 | /// 26 | /// If true, when the scrollbar value changes the offset of the child entities will smoothly interpolate to the new offset. 27 | /// If false, offset will be set immediately. 28 | /// 29 | public bool InterpolateScrollbarOffset = true; 30 | 31 | /// 32 | /// Interpolation speed, if scrollbar position is interpolated. 33 | /// 34 | public float ScrollbarInterpolationSpeed = 10f; 35 | 36 | // scrollbar interpolation value 37 | float _scrollbarOffset = 0f; 38 | 39 | /// 40 | /// Create the panel. 41 | /// 42 | /// Parent UI system. 43 | /// Panel stylesheet. 44 | public Panel(UISystem system, StyleSheet? stylesheet) : base(system, stylesheet) 45 | { 46 | } 47 | 48 | /// 49 | /// Create the panel with default stylesheets. 50 | /// 51 | /// Parent UI system. 52 | public Panel(UISystem system) : this(system, system.DefaultStylesheets.Panels) 53 | { 54 | } 55 | 56 | /// 57 | internal override void PerformMouseWheelScroll(int val) 58 | { 59 | if ((VerticalScrollbar != null) && (VerticalScrollbar.Visible)) 60 | { 61 | VerticalScrollbar.PerformMouseWheelScroll(val); 62 | } 63 | else 64 | { 65 | base.PerformMouseWheelScroll(val); 66 | } 67 | } 68 | 69 | /// 70 | protected override void Update(float dt) 71 | { 72 | base.Update(dt); 73 | 74 | // update scrollbar 75 | if ((VerticalScrollbar != null) && IsCurrentlyVisible()) 76 | { 77 | // scrollbar max value 78 | if (_autoSetScrollbarMax) 79 | { 80 | var maxScrollbarHeight = CalculateMaxScrollbarValue(); 81 | if (maxScrollbarHeight > 1) 82 | { 83 | VerticalScrollbar.MaxValue = maxScrollbarHeight; 84 | VerticalScrollbar.KeyboardStep = VerticalScrollbar.MouseWheelStep = -Math.Clamp(maxScrollbarHeight / 10, 1, 100); 85 | VerticalScrollbar.Enabled = true; 86 | } 87 | } 88 | 89 | // current scroll value 90 | float scrollbarNewValue = -VerticalScrollbar.Value; 91 | _scrollbarOffset = InterpolateScrollbarOffset ? MathUtils.Lerp(_scrollbarOffset, scrollbarNewValue, dt * ScrollbarInterpolationSpeed) : scrollbarNewValue; 92 | } 93 | } 94 | 95 | /// 96 | /// Called after drawing a child. 97 | /// 98 | protected override void PostDrawingChild(DrawMethodResult? drawResult) 99 | { 100 | if (_autoSetScrollbarMax && (VerticalScrollbar != null) && (drawResult != null)) 101 | { 102 | _maxHeightForScrollbar = Math.Max(_maxHeightForScrollbar, drawResult.Value.BoundingRect.Bottom - LastInternalBoundingRect.Top - LastInternalBoundingRect.Height); 103 | } 104 | } 105 | int _maxHeightForScrollbar = 1; 106 | 107 | /// 108 | /// Calculate scrollbar max value. 109 | /// 110 | protected virtual int CalculateMaxScrollbarValue() 111 | { 112 | return _maxHeightForScrollbar; 113 | } 114 | 115 | /// 116 | protected override MeasureVector GetDefaultEntityTypeSize() 117 | { 118 | var ret = new MeasureVector(); 119 | ret.SetPixels(400, 400); 120 | return ret; 121 | } 122 | 123 | /// 124 | protected override Point GetScrollOffset() 125 | { 126 | if (VerticalScrollbar != null) 127 | { 128 | return new Point(0, (int)_scrollbarOffset); 129 | } 130 | return Point.Zero; 131 | } 132 | 133 | /// 134 | protected override Sides GetScrollExtraPadding() 135 | { 136 | if (VerticalScrollbar != null && VerticalScrollbar.Visible) 137 | { 138 | if (VerticalScrollbar.Anchor == Anchor.TopLeft || VerticalScrollbar.Anchor == Anchor.CenterLeft || VerticalScrollbar.Anchor == Anchor.BottomLeft) 139 | { 140 | return new Sides(VerticalScrollbar.LastBoundingRect.Width + VerticalScrollbar.GetMarginAfter().X, 0, 0, 0); 141 | } 142 | else if (VerticalScrollbar.Anchor == Anchor.TopRight || VerticalScrollbar.Anchor == Anchor.CenterRight || VerticalScrollbar.Anchor == Anchor.BottomRight) 143 | { 144 | return new Sides(0, VerticalScrollbar.LastBoundingRect.Width + VerticalScrollbar.GetMarginBefore().X, 0, 0); 145 | } 146 | } 147 | return Sides.Zero; 148 | } 149 | 150 | /// 151 | /// Create a vertical scrollbar for this panel with default stylesheet. 152 | /// 153 | /// If true, will set the scrollbar max value automatically based on panel height vs. most-bottom entity. 154 | public void CreateVerticalScrollbar(bool autoSetScrollbarMax = true) 155 | { 156 | CreateVerticalScrollbar( 157 | UISystem.DefaultStylesheets.VerticalScrollbars ?? UISystem.DefaultStylesheets.VerticalSliders, 158 | UISystem.DefaultStylesheets.VerticalScrollbarsHandle ?? UISystem.DefaultStylesheets.VerticalSlidersHandle); 159 | } 160 | 161 | /// 162 | /// Create a vertical scrollbar for this panel. 163 | /// 164 | /// Vertical scrollbar style. 165 | /// Vertical scrollbar handle style. 166 | /// If true, will set the scrollbar max value automatically based on panel height vs. most-bottom entity. 167 | public void CreateVerticalScrollbar(StyleSheet? stylesheet, StyleSheet? handleStylesheet, bool autoSetScrollbarMax = true) 168 | { 169 | VerticalScrollbar = new Slider(UISystem, stylesheet, handleStylesheet, Orientation.Vertical); 170 | VerticalScrollbar.Anchor = Anchor.TopRight; 171 | VerticalScrollbar.IncludeInInternalAutoAnchorCalculation = false; 172 | VerticalScrollbar.Value = 0; 173 | VerticalScrollbar.MaxValue = 0; 174 | VerticalScrollbar.FlippedDirection = true; 175 | VerticalScrollbar.KeyboardStep = VerticalScrollbar.MouseWheelStep = -1; 176 | VerticalScrollbar.IgnoreScrollOffset = true; 177 | _autoSetScrollbarMax = autoSetScrollbarMax; 178 | AddChildInternal(VerticalScrollbar, true); 179 | VerticalScrollbar.IgnoreScrollOffset = true; 180 | } 181 | 182 | // should we auto-set scrollbar max value? 183 | bool _autoSetScrollbarMax; 184 | 185 | /// 186 | /// Remove the vertical scrollbar for this panel, if set. 187 | /// 188 | public void RemoveVerticalScrollbar() 189 | { 190 | if (VerticalScrollbar != null) 191 | { 192 | RemoveChildInternal(VerticalScrollbar); 193 | VerticalScrollbar = null; 194 | } 195 | } 196 | 197 | /// 198 | internal override void DoInteractions(InputState inputState) 199 | { 200 | base.DoInteractions(inputState); 201 | 202 | // if got scrollbar, apply wheel to it 203 | if ((inputState.MouseWheelChange != 0) && (VerticalScrollbar != null)) 204 | { 205 | VerticalScrollbar.PerformMouseWheelScroll(inputState.MouseWheelChange); 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Iguina/Entities/ProgressBar.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Utils; 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// Progress bars are like sliders, but instead of moving a handle they 'fill' an internal entity. 8 | /// These entities are useful to show progress, health bars, etc. 9 | /// 10 | /// By default, progress bars have 'IgnoreInteractions' set to true. If you want the progress bar to behave like a slider and allow users to change its value, set it to false. 11 | public class ProgressBar : Slider 12 | { 13 | /// 14 | /// Create the progress bar. 15 | /// 16 | /// Parent UI system. 17 | /// Progress bar stylesheet. 18 | /// Progress bar fill stylesheet. 19 | /// Progress bar orientation. 20 | public ProgressBar(UISystem system, StyleSheet? stylesheet, StyleSheet? fillStylesheet, Orientation orientation = Orientation.Horizontal) : base(system, stylesheet, fillStylesheet, orientation) 21 | { 22 | Handle.IgnoreInteractions = IgnoreInteractions = true; 23 | Handle.Anchor = Handle.StyleSheet.DefaultAnchor ?? stylesheet?.DefaultAnchor ?? ((orientation == Orientation.Horizontal) ? Anchor.CenterLeft : Anchor.TopCenter); 24 | } 25 | 26 | /// 27 | /// Create the progress bar with default stylesheets. 28 | /// 29 | /// Parent UI system. 30 | /// Progress bar orientation. 31 | public ProgressBar(UISystem system, Orientation orientation = Orientation.Horizontal) : 32 | this(system, 33 | (orientation == Orientation.Horizontal) ? system.DefaultStylesheets.HorizontalProgressBars : system.DefaultStylesheets.VerticalProgressBars, 34 | (orientation == Orientation.Horizontal) ? system.DefaultStylesheets.HorizontalProgressBarsFill : system.DefaultStylesheets.VerticalProgressBarsFill, 35 | orientation) 36 | { 37 | } 38 | 39 | /// 40 | protected override void Update(float dt) 41 | { 42 | base.Update(dt); 43 | Handle.IgnoreInteractions = IgnoreInteractions; 44 | } 45 | 46 | /// 47 | protected override void UpdateHandle(float dt) 48 | { 49 | var valuePercent = ValuePercent; 50 | if (Orientation == Orientation.Horizontal) 51 | { 52 | if (InterpolateHandlePosition) 53 | { 54 | var currValue = Handle.Size.X.Value; 55 | Handle.Size.X.SetPercents(MathUtils.Lerp(currValue, valuePercent * 100f, dt * HandleInterpolationSpeed)); 56 | } 57 | else 58 | { 59 | Handle.Size.X.SetPercents(valuePercent * 100f); 60 | } 61 | } 62 | else 63 | { 64 | if (InterpolateHandlePosition) 65 | { 66 | var currValue = Handle.Size.Y.Value; 67 | Handle.Size.Y.SetPercents(MathUtils.Lerp(currValue, valuePercent * 100f, dt * HandleInterpolationSpeed)); 68 | } 69 | else 70 | { 71 | Handle.Size.Y.SetPercents(valuePercent * 100f); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Iguina/Entities/RadioButton.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | namespace Iguina.Entities 4 | { 5 | /// 6 | /// Radio button entity type. 7 | /// 8 | public class RadioButton : CheckedEntity 9 | { 10 | /// 11 | /// Radio button label. 12 | /// 13 | public Paragraph Paragraph { get; private set; } 14 | 15 | /// 16 | internal override bool Interactable => true; 17 | 18 | 19 | /// 20 | /// Create the radio button. 21 | /// 22 | /// Parent UI system. 23 | /// Radio button stylesheet. 24 | /// Radio button text. 25 | public RadioButton(UISystem system, StyleSheet? stylesheet, string text = "New Radio Button") : base(system, stylesheet) 26 | { 27 | // create the radio button paragraph 28 | Paragraph = new Paragraph(system, stylesheet, text); 29 | Paragraph.DrawFillTexture = false; 30 | AddChildInternal(Paragraph); 31 | Paragraph.CopyStateFrom = this; 32 | 33 | // make checkable 34 | ToggleCheckOnClick = true; 35 | CanClickToUncheck = false; 36 | ExclusiveSelection = true; 37 | } 38 | 39 | /// 40 | /// Create the radio button with default stylesheets. 41 | /// 42 | /// Parent UI system. 43 | /// Radio button text. 44 | public RadioButton(UISystem system, string text = "New Radio Button") : this(system, 45 | system.DefaultStylesheets.RadioButtons ?? system.DefaultStylesheets.CheckBoxes, 46 | text) 47 | { 48 | } 49 | 50 | /// 51 | protected override MeasureVector GetDefaultEntityTypeSize() 52 | { 53 | var ret = new MeasureVector(); 54 | ret.X.SetPercents(100f); 55 | ret.Y.SetPixels(54); 56 | return ret; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Iguina/Entities/RowsSpacer.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | namespace Iguina.Entities 4 | { 5 | /// 6 | /// An entity that doesn't do anything except for creating vertical space between entities that are set with auto anchor. 7 | /// 8 | public class RowsSpacer : Entity 9 | { 10 | /// 11 | /// Create the spacer. 12 | /// 13 | /// Parent UI system. 14 | /// How many empty 'rows' to to create. The height of each row is defined by the UI system stylesheet. 15 | public RowsSpacer(UISystem system, int rowsCount = 1) : base(system, null) 16 | { 17 | IgnoreInteractions = true; 18 | Size.Y.SetPixels(system.SystemStyleSheet.RowSpaceHeight * rowsCount); 19 | } 20 | 21 | /// 22 | protected override Anchor GetDefaultEntityTypeAnchor() 23 | { 24 | return Anchor.AutoCenter; 25 | } 26 | 27 | /// 28 | protected override MeasureVector GetDefaultEntityTypeSize() 29 | { 30 | var ret = new MeasureVector(); 31 | ret.X.SetPercents(100f); 32 | ret.Y.SetPixels(8); 33 | return ret; 34 | } 35 | 36 | /// 37 | internal override void DebugDraw(bool debugDrawChildren) 38 | { 39 | if (!Visible) { return; } 40 | 41 | UISystem.Renderer.DrawRectangle(LastBoundingRect, new Color(255, 0, 0, 65)); 42 | 43 | if (debugDrawChildren) 44 | { 45 | IterateChildren((Entity child) => { 46 | child.DebugDraw(debugDrawChildren); 47 | return true; 48 | }); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Iguina/Entities/Slider.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | using Iguina.Utils; 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// A slider to get numeric input. 8 | /// 9 | public class Slider : Entity 10 | { 11 | /// 12 | /// The entity used as the slider handle. 13 | /// 14 | public Entity Handle { get; private set; } 15 | 16 | /// 17 | /// Slider orientation. 18 | /// 19 | public Orientation Orientation { get; private set; } 20 | 21 | /// 22 | /// How much to change value via mouse wheel scroll. 23 | /// 24 | public int MouseWheelStep = 1; 25 | 26 | /// 27 | /// How much to change value via keyboard interactions. 28 | /// 29 | public int KeyboardStep = 1; 30 | 31 | // current handle offset 32 | float _currHandleOffset; 33 | 34 | /// 35 | internal override bool LockFocusWhileMouseDown => true; 36 | 37 | /// 38 | internal override bool CanGetFocusWhileMouseIsDown => false; 39 | 40 | /// 41 | internal override bool Interactable => true; 42 | 43 | /// 44 | /// Slider min value. 45 | /// 46 | public virtual int MinValue 47 | { 48 | get => _minValue; 49 | set 50 | { 51 | if (_minValue != value) 52 | { 53 | _minValue = value; 54 | if (_maxValue < _minValue) { throw new ArgumentOutOfRangeException(nameof(value), "Slider min value must be smaller than max value!"); } 55 | if (_value < _minValue) 56 | { 57 | _value = _minValue; 58 | } 59 | } 60 | } 61 | } 62 | int _minValue = 0; 63 | 64 | /// 65 | /// Slider max value. 66 | /// 67 | public virtual int MaxValue 68 | { 69 | get => _maxValue; 70 | set 71 | { 72 | if (_maxValue != value) 73 | { 74 | _maxValue = value; 75 | if (_maxValue < _minValue) { throw new ArgumentOutOfRangeException(nameof(value), "Slider max value must be bigger than min value!"); } 76 | if (_value > _maxValue) 77 | { 78 | _value = _maxValue; 79 | } 80 | } 81 | } 82 | } 83 | int _maxValue = 10; 84 | 85 | /// 86 | /// Set / get current value. 87 | /// 88 | public int Value 89 | { 90 | get => _value; 91 | set 92 | { 93 | if (_value != value) 94 | { 95 | if (value < MinValue) { throw new ArgumentOutOfRangeException(nameof(value), "Slider value can't be smaller than min value!"); } 96 | if (value > MaxValue) { throw new ArgumentOutOfRangeException(nameof(value), "Slider value can't be bigger than max value!"); } 97 | _value = value; 98 | Events.OnValueChanged?.Invoke(this); 99 | UISystem.Events.OnValueChanged?.Invoke(this); 100 | } 101 | } 102 | } 103 | int _value = 5; 104 | 105 | /// 106 | /// Set current value, after clamping it to be between min and max. 107 | /// 108 | public int ValueSafe 109 | { 110 | get => Value; 111 | set 112 | { 113 | Value = Math.Clamp(value, MinValue, MaxValue); 114 | } 115 | } 116 | 117 | /// 118 | /// Slider steps count (or 0 for MaxValue - MinValue). 119 | /// 120 | public uint StepsCount 121 | { 122 | get => _stepsCount; 123 | set 124 | { 125 | if (_stepsCount != value) 126 | { 127 | _stepsCount = value; 128 | if (value > ValueRange) 129 | { 130 | throw new ArgumentOutOfRangeException(nameof(value), "Slider steps count can't be bigger than ValueRange (max - min) value!"); 131 | } 132 | Value = MinValue; 133 | } 134 | } 135 | } 136 | uint _stepsCount = 0; 137 | 138 | /// 139 | /// Get value, as percent between 0.f and 1.f. 140 | /// 141 | public float ValuePercent => (float)(Value - MinValue) / (float)(MaxValue - MinValue); 142 | 143 | /// 144 | /// If true, slider direction will be flipped. 145 | /// 146 | public bool FlippedDirection = false; 147 | 148 | // so we can point and drag handle 149 | /// 150 | protected override bool WalkInternalChildren => true; 151 | 152 | /// 153 | /// Get the value range based on max and min values. 154 | /// 155 | public int ValueRange => MaxValue - MinValue; 156 | 157 | /// 158 | /// If true, this slider will set its range (min, max, and steps count) automatically, based on the entity size. 159 | /// 160 | public bool AutoSetRange = false; 161 | 162 | /// 163 | /// Create the slider. 164 | /// 165 | /// Parent UI system. 166 | /// Slider stylesheet. 167 | /// Slider handle stylesheet. 168 | /// Slider orientation. 169 | public Slider(UISystem system, StyleSheet? stylesheet, StyleSheet? handleStylesheet, Orientation orientation = Orientation.Horizontal) : base(system, stylesheet) 170 | { 171 | // set orientation and call default anchor and size again 172 | Orientation = orientation; 173 | CalculateDefaultAnchorAndSize(); 174 | 175 | // create handle 176 | Handle = new Entity(system, handleStylesheet); 177 | Handle.CopyStateFrom = this; 178 | Handle.Anchor = (orientation == Orientation.Horizontal) ? Anchor.CenterLeft : Anchor.TopCenter; 179 | Handle.TransferInteractionsTo = this; 180 | AddChildInternal(Handle); 181 | Handle.IgnoreScrollOffset = true; 182 | } 183 | 184 | /// 185 | /// Create the slider with default stylesheets. 186 | /// 187 | /// Parent UI system. 188 | /// Slider orientation. 189 | public Slider(UISystem system, Orientation orientation = Orientation.Horizontal) : 190 | this(system, 191 | (orientation == Orientation.Horizontal) ? system.DefaultStylesheets.HorizontalSliders : system.DefaultStylesheets.VerticalSliders, 192 | (orientation == Orientation.Horizontal) ? system.DefaultStylesheets.HorizontalSlidersHandle : system.DefaultStylesheets.VerticalSlidersHandle, 193 | orientation) 194 | { 195 | } 196 | 197 | /// 198 | internal override void DoInteractions(InputState inputState) 199 | { 200 | // do base interactions 201 | base.DoInteractions(inputState); 202 | 203 | // special case - max value is 0 204 | if (ValueRange <= 0) 205 | { 206 | return; 207 | } 208 | 209 | // select value via mouse 210 | if (inputState.LeftMouseDown) 211 | { 212 | // get default steps count and step size 213 | var stepsCount = (int)StepsCount; 214 | var valueRange = ValueRange; 215 | if (stepsCount == 0) { stepsCount = valueRange; } 216 | var stepSize = valueRange / stepsCount; 217 | 218 | // get value in percent 219 | float valuePercent = (Orientation == Orientation.Horizontal) ? 220 | (((float)inputState.MousePosition.X - LastInternalBoundingRect.Left) / (float)LastInternalBoundingRect.Width) : 221 | (((float)inputState.MousePosition.Y - LastInternalBoundingRect.Top) / (float)LastInternalBoundingRect.Height); 222 | if (float.IsNaN(valuePercent)) { valuePercent = 0f; } 223 | if (valuePercent < 0f) { valuePercent = 0f; } 224 | if (valuePercent > 1f) { valuePercent = 1f; } 225 | if (((Orientation == Orientation.Horizontal) && FlippedDirection) || 226 | ((Orientation == Orientation.Vertical) && !FlippedDirection)) 227 | { 228 | valuePercent = 1f - valuePercent; 229 | } 230 | int relativeValue = (int)(MathF.Round((valuePercent * valueRange) / stepSize) * stepSize); 231 | Value = MinValue + (int)relativeValue; 232 | } 233 | } 234 | 235 | /// 236 | internal override void DoFocusedEntityInteractions(InputState inputState) 237 | { 238 | // call base class to trigger events 239 | base.DoFocusedEntityInteractions(inputState); 240 | 241 | // move value via keyboard - horizontal 242 | if (Orientation == Orientation.Horizontal) 243 | { 244 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveLeft) 245 | { 246 | ValueSafe -= KeyboardStep; 247 | } 248 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveRight) 249 | { 250 | ValueSafe += KeyboardStep; 251 | } 252 | } 253 | else 254 | { 255 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveUp) 256 | { 257 | ValueSafe += KeyboardStep; 258 | } 259 | if (inputState.KeyboardInteraction == Drivers.KeyboardInteractions.MoveDown) 260 | { 261 | ValueSafe -= KeyboardStep; 262 | } 263 | } 264 | } 265 | 266 | /// 267 | internal override void PerformMouseWheelScroll(int val) 268 | { 269 | if (MouseWheelStep != 0) 270 | { 271 | if (val > 0) 272 | { 273 | Value = Math.Clamp(Value + MouseWheelStep, MinValue, MaxValue); 274 | } 275 | if (val < 0) 276 | { 277 | Value = Math.Clamp(Value - MouseWheelStep, MinValue, MaxValue); 278 | } 279 | } 280 | } 281 | 282 | /// 283 | protected override void Update(float dt) 284 | { 285 | base.Update(dt); 286 | if (AutoSetRange) 287 | { 288 | SetAutoRange(); 289 | } 290 | UpdateHandle(dt); 291 | } 292 | 293 | /// 294 | /// Auto set slider min, max, and steps count. 295 | /// 296 | protected virtual void SetAutoRange() 297 | { 298 | MinValue = 0; 299 | if (Orientation == Orientation.Horizontal) 300 | { 301 | MaxValue = Math.Max(LastBoundingRect.Width, 10); 302 | } 303 | else 304 | { 305 | MaxValue = Math.Max(LastBoundingRect.Height, 10); 306 | } 307 | StepsCount = (uint)MaxValue; 308 | } 309 | 310 | /// 311 | /// Update slider handle offset. 312 | /// 313 | protected virtual void UpdateHandle(float dt) 314 | { 315 | // not visible? skip 316 | if (!IsCurrentlyVisible()) { return; } 317 | 318 | // special case - no value range 319 | if (ValueRange <= 0) 320 | { 321 | Handle.Visible = false; 322 | return; 323 | } 324 | Handle.Visible = true; 325 | 326 | // get relative value 327 | int relativeValue = Value - MinValue; 328 | 329 | // set horizontal handle position 330 | if (Orientation == Orientation.Horizontal) 331 | { 332 | var offsetX = FlippedDirection ? 333 | (int)((1f - (float)relativeValue / ValueRange) * LastInternalBoundingRect.Width) : 334 | (int)(((float)relativeValue / ValueRange) * LastInternalBoundingRect.Width); 335 | float newOffset = offsetX - Handle.LastBoundingRect.Width / 2; 336 | _currHandleOffset = InterpolateHandlePosition ? 337 | MathUtils.Lerp(_currHandleOffset, newOffset, dt * HandleInterpolationSpeed) : newOffset; 338 | Handle.Offset.X.SetPixels((int)_currHandleOffset); 339 | } 340 | // set vertical handle position 341 | else 342 | { 343 | var offsetY = FlippedDirection ? 344 | (int)(((float)relativeValue / ValueRange) * LastInternalBoundingRect.Height) : 345 | (int)((1f - (float)relativeValue / ValueRange) * LastInternalBoundingRect.Height); 346 | float newOffset = offsetY - Handle.LastBoundingRect.Height / 2; 347 | _currHandleOffset = InterpolateHandlePosition ? 348 | MathUtils.Lerp(_currHandleOffset, newOffset, dt * HandleInterpolationSpeed) : newOffset; 349 | Handle.Offset.Y.SetPixels((int)_currHandleOffset); 350 | } 351 | } 352 | 353 | /// 354 | protected override MeasureVector GetDefaultEntityTypeSize() 355 | { 356 | var ret = new MeasureVector(); 357 | if (Orientation == Orientation.Horizontal) 358 | { 359 | ret.X.SetPercents(100f); 360 | ret.Y.SetPixels(16); 361 | } 362 | else 363 | { 364 | ret.Y.SetPercents(100f); 365 | ret.X.SetPixels(16); 366 | } 367 | return ret; 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /Iguina/Entities/Title.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// A title text entity. 8 | /// Same as a paragraph, but with different defaults values and stylesheet. 9 | /// 10 | public class Title : Paragraph 11 | { 12 | /// 13 | /// Create the title. 14 | /// 15 | /// Parent UI system. 16 | /// Title stylesheet. 17 | /// Title text. 18 | /// If true, this title will ignore user interactions. 19 | public Title(UISystem system, StyleSheet? stylesheet, string text = "New Title", bool ignoreInteractions = true) : base(system, stylesheet, text, ignoreInteractions) 20 | { 21 | } 22 | 23 | /// 24 | /// Create the title with default stylesheets. 25 | /// 26 | /// Parent UI system. 27 | /// Title text. 28 | /// If true, this title will ignore user interactions. 29 | public Title(UISystem system, string text = "New Title", bool ignoreInteractions = true) : this(system, system.DefaultStylesheets.Titles, text, ignoreInteractions) 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Iguina/Entities/VerticalLine.cs: -------------------------------------------------------------------------------- 1 | using Iguina.Defs; 2 | 3 | 4 | namespace Iguina.Entities 5 | { 6 | /// 7 | /// A graphical vertical line that separates between entities. 8 | /// 9 | public class VerticalLine : Entity 10 | { 11 | /// 12 | /// Create the vertical line. 13 | /// 14 | /// Parent UI system. 15 | /// Vertical line stylesheet. 16 | public VerticalLine(UISystem system, StyleSheet? stylesheet) : base(system, stylesheet) 17 | { 18 | IgnoreInteractions = true; 19 | } 20 | 21 | /// 22 | /// Create the vertical line with default stylesheets. 23 | /// 24 | /// Parent UI system. 25 | public VerticalLine(UISystem system) : this(system, system.DefaultStylesheets.VerticalLines) 26 | { 27 | } 28 | 29 | /// 30 | protected override Anchor GetDefaultEntityTypeAnchor() 31 | { 32 | return Anchor.AutoInlineLTR; 33 | } 34 | 35 | /// 36 | protected override MeasureVector GetDefaultEntityTypeSize() 37 | { 38 | var ret = new MeasureVector(); 39 | ret.X.SetPixels(8); 40 | ret.Y.SetPercents(100f); 41 | return ret; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Iguina/Iguina.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 1.1.3 6 | enable 7 | enable 8 | True 9 | A framework-agnostic, cross platform, modern UI system for C# 10 | RonenNess 11 | Ronen Ness 12 | 13 | logo_no_text.png 14 | Iguina is a framework-agnostic, cross platform, modern UI system for C#. 15 | 16 | Its a spiritual successor to the GeonBit.UI library, but takes a more flexible and modern approach, and can be used with any framework, and not just MonoGame. 17 | 18 | readme.md 19 | https://github.com/RonenNess/Iguina 20 | UI;GUI;Game;User Interface;UX;MonoGame;RayLib 21 | LICENSE 22 | 23 | 24 | 25 | 26 | True 27 | \ 28 | 29 | 30 | True 31 | \ 32 | 33 | 34 | True 35 | \ 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Iguina/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\Release\net8.0\publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | 13 | -------------------------------------------------------------------------------- /Iguina/Utils/MathUtils.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Iguina.Utils 3 | { 4 | /// 5 | /// Math utils. 6 | /// 7 | internal static class MathUtils 8 | { 9 | /// 10 | /// Lerp numeric value. 11 | /// 12 | public static float Lerp(float a, float b, float t) 13 | { 14 | return a + (b - a) * t; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Iguina/readme.md: -------------------------------------------------------------------------------- 1 | # Iguina 2 | 3 | The main Iguina project. 4 | All the code is here. 5 | 6 | For full documentation, check out the readme file at the repository root. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ronen Ness 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 | -------------------------------------------------------------------------------- /ReadmeAssets/anchors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/anchors.png -------------------------------------------------------------------------------- /ReadmeAssets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/demo.gif -------------------------------------------------------------------------------- /ReadmeAssets/entity-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-button.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-checkbox.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-colorpicker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-colorpicker.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-colorslider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-colorslider.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-dropdown.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-hl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-hl.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-list.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-numericinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-numericinput.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-panel.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-paragraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-paragraph.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-progressbars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-progressbars.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-sliders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-sliders.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-textinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-textinput.png -------------------------------------------------------------------------------- /ReadmeAssets/entity-vl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/entity-vl.png -------------------------------------------------------------------------------- /ReadmeAssets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/logo.png -------------------------------------------------------------------------------- /ReadmeAssets/logo_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/logo_bg.png -------------------------------------------------------------------------------- /ReadmeAssets/logo_no_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/logo_no_text.png -------------------------------------------------------------------------------- /ReadmeAssets/mg_basic_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/mg_basic_example.png -------------------------------------------------------------------------------- /ReadmeAssets/rl_basic_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/rl_basic_example.png -------------------------------------------------------------------------------- /ReadmeAssets/save-file-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RonenNess/Iguina/4170370c9b949b8218c2fd026266a75d3cc899c3/ReadmeAssets/save-file-dialog.png -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | - GridView object. 2 | - TreeBox (like ListBox but with hierarchy). 3 | - CollapsiblePanel (panel that can open / close). 4 | - TabsView (multiple panels that you can switch between with tab buttons). --------------------------------------------------------------------------------