From c0fdd8fed18e8365767fd1486644f01bd8048c28 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 18:21:35 +1300 Subject: [PATCH 01/38] Added some native functions/types and wrappers. --- Library/EnumVariable.cs | 20 +++--- Library/Native.cs | 132 +++++++++++++++++++++++++++++++++++++- Library/StringVariable.cs | 4 +- Library/Types.cs | 23 +++++-- README.md | 5 ++ 5 files changed, 162 insertions(+), 22 deletions(-) diff --git a/Library/EnumVariable.cs b/Library/EnumVariable.cs index 7a02548..93a0c32 100644 --- a/Library/EnumVariable.cs +++ b/Library/EnumVariable.cs @@ -66,18 +66,16 @@ public T Value private static void InitEnumVariable(Variable var, String id) { if (!typeof(T).IsEnum) { - throw new InvalidOperationException(String.Format("Type {0} is not an enumeration.", typeof(T).FullName)); + throw new ArgumentException(String.Format("Type {0} is not an enumeration.", typeof(T).FullName)); } - var enumNames = String.Join(",", typeof(T).GetEnumNames()); - var it = var as EnumVariable; Variable.SetCallbacks.Add(id, new Tw.SetVarCallback(it.SetCallback)); Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); Tw.AddVarCB(var.ParentBar.Pointer, id, - Tw.DefineEnumFromString(typeof(T).FullName, enumNames), + Tw.DefineEnum(Guid.NewGuid().ToString(), GetEnumLabels()), Variable.SetCallbacks[id], Variable.GetCallbacks[id], IntPtr.Zero, null); @@ -93,7 +91,6 @@ public EnumVariable(Bar bar, T initialValue, String def = null) : base(bar, InitEnumVariable, def) { Validating += (s, e) => { e.Valid = Enum.IsDefined(typeof(T), e.Value); }; - Tw.SetParam(ParentBar.Pointer, ID, "enum", GetEnumString()); ValidateAndSet(initialValue); } @@ -150,20 +147,19 @@ private void GetCallback(IntPtr pointer, IntPtr clientData) } /// - /// Returns a formatted key-value representation of this enum. + /// Returns the enum type's values and labels/descriptions. /// - private static String GetEnumString() + private static IDictionary GetEnumLabels() { - IList enumList = new List(); + var labels = new Dictionary(); foreach (var kv in ((int[])Enum.GetValues(typeof(T))).Zip(typeof(T).GetEnumNames(), (i, n) => new Tuple(i, n))) { - var valueAttributes = typeof(T).GetMember(kv.Item2)[0].GetCustomAttributes(typeof(DescriptionAttribute), false); - string label = valueAttributes.Any() ? ((DescriptionAttribute)valueAttributes.First()).Description : kv.Item2; - enumList.Add(String.Format("{0} {{{1}}}", kv.Item1, label)); // Follows the AntTweakBar enum string format. + var attr = typeof(T).GetMember(kv.Item2)[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + labels.Add(kv.Item1, attr.Any() ? ((DescriptionAttribute)attr.First()).Description : kv.Item2); } - return String.Join(",", enumList); + return labels; } public override String ToString() diff --git a/Library/Native.cs b/Library/Native.cs index ca99ae4..40fd336 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -86,6 +86,11 @@ internal static class NativeMethods [In] IntPtr wParam, [In] IntPtr lParam); + [DllImport(DLLName, EntryPoint = "TwEventX11")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern Boolean TwEventX11( + [In] IntPtr xEvent); + [DllImport(DLLName, EntryPoint = "TwSetCurrentWindow")] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean TwSetCurrentWindow( @@ -274,11 +279,26 @@ internal static class NativeMethods [In] IntPtr clientData, [In, MarshalAs(UnmanagedType.LPStr)] String def); + [DllImport(DLLName, EntryPoint = "TwDefineEnum", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern Tw.VariableType TwDefineEnum( + [In, MarshalAs(UnmanagedType.LPStr)] String name, + [In] EnumVal[] enumValues, + [In] uint numValues); + [DllImport(DLLName, EntryPoint = "TwDefineEnumFromString", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] public static extern Tw.VariableType TwDefineEnumFromString( [In, MarshalAs(UnmanagedType.LPStr)] String name, [In, MarshalAs(UnmanagedType.LPStr)] String enumString); + [DllImport(DLLName, EntryPoint = "TwDefineStruct", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] + public static extern Tw.VariableType TwDefineStruct( + [In, MarshalAs(UnmanagedType.LPStr)] String name, + [In] StructMember[] structMembers, + [In] uint numMembers, + [In] UIntPtr structSize, + [In] Tw.SummaryCallback callback, + [In] IntPtr clientData); + [DllImport(DLLName, EntryPoint = "TwRemoveVar", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean TwRemoveVar( @@ -289,6 +309,36 @@ internal static class NativeMethods [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean TwRemoveAllVars( [In] IntPtr bar); + + [StructLayout(LayoutKind.Sequential)] + internal struct EnumVal + { + public int Value; + public IntPtr Label; + + public EnumVal(int value, IntPtr label) : this() + { + Value = value; + Label = label; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct StructMember + { + public IntPtr Name; + public Tw.VariableType Type; + public UIntPtr Offset; + public IntPtr DefString; + + public StructMember(IntPtr name, Tw.VariableType type, UIntPtr offset, IntPtr defString) : this() + { + Name = name; + Type = type; + Offset = offset; + DefString = defString; + } + } } /// @@ -441,6 +491,19 @@ public static bool EventWin(IntPtr wnd, int msg, IntPtr wParam, IntPtr lParam) return NativeMethods.TwEventWin(wnd, msg, wParam, lParam); } + /// + /// The X11 event handler. + /// + /// Whether the event has been handled by AntTweakBar. + public static bool EventX11(IntPtr xEvent) + { + if (xEvent == IntPtr.Zero) { + throw new ArgumentOutOfRangeException("xEvent"); + } + + return NativeMethods.TwEventX11(xEvent); + } + /// /// This function is intended to be used by applications with multiple graphical windows. It tells AntTweakBar to switch its current context to the context associated to the identifier windowID. /// @@ -1032,12 +1095,48 @@ public static void AddButton(IntPtr bar, String name, ButtonCallback callback, I } } + /// + /// This function creates a new variable type corresponding to an enum. + /// + /// Specify a name for the enum type (must be unique). + /// A mapping from admissible values to their labels. + public static VariableType DefineEnum(String name, IDictionary labels) + { + if (name == null) { + throw new ArgumentNullException("name"); + } else if (labels == null) { + throw new ArgumentNullException("labels"); + } + + var values = new List(); + + try + { + foreach (var kv in labels) { + values.Add(new NativeMethods.EnumVal(kv.Key, Helpers.PtrFromStr(kv.Value))); + } + + VariableType enumType = NativeMethods.TwDefineEnum(name, values.ToArray(), (uint)values.Count); + + if (enumType == VariableType.Undefined) { + throw new AntTweakBarException("TwDefineEnum failed."); + } + + return enumType; + } + finally + { + foreach (var value in values) { + Marshal.FreeCoTaskMem(value.Label); + } + } + } + /// /// This function creates a new variable type corresponding to an enum. /// /// Specify a name for the enum type (must be unique). /// Comma-separated list of labels. - /// public static VariableType DefineEnumFromString(String name, String enumString) { if (name == null) { @@ -1046,9 +1145,9 @@ public static VariableType DefineEnumFromString(String name, String enumString) throw new ArgumentNullException("enumString"); } - VariableType enumType; + VariableType enumType = NativeMethods.TwDefineEnumFromString(name, enumString); - if ((enumType = NativeMethods.TwDefineEnumFromString(name, enumString)) == VariableType.Undefined) { + if (enumType == VariableType.Undefined) { throw new AntTweakBarException("TwDefineEnumFromString failed."); } @@ -1090,6 +1189,9 @@ public static void RemoveAllVars(IntPtr bar) /// internal static class Helpers { + /// + /// Decodes a UTF-8 string from a pointer. + /// public static String StrFromPtr(IntPtr ptr) { var strBytes = new List(); @@ -1103,5 +1205,29 @@ public static String StrFromPtr(IntPtr ptr) return Encoding.UTF8.GetString(strBytes.ToArray()); } + + /// + /// Allocates a new pointer containing the UTF-8 string. + /// + /// + /// The pointer must be freed later with FreeCoTaskMem. + /// + public static IntPtr PtrFromStr(String str) + { + var cnt = Encoding.UTF8.GetByteCount(str); + var ptr = Marshal.AllocCoTaskMem(cnt + 1); + CopyStrToPtr(ptr, str); + return ptr; + } + + /// + /// Encodes a UTF-8 string into a pointer. + /// + public static void CopyStrToPtr(IntPtr ptr, String str) + { + var bytes = new List(Encoding.UTF8.GetBytes(str)); + bytes.Add(0); // append the null-terminated character + Marshal.Copy(bytes.ToArray(), 0, ptr, bytes.Count); + } } } diff --git a/Library/StringVariable.cs b/Library/StringVariable.cs index cfe149a..4ccd51b 100644 --- a/Library/StringVariable.cs +++ b/Library/StringVariable.cs @@ -140,9 +140,7 @@ private void SetCallback(IntPtr pointer, IntPtr clientData) /// private void GetCallback(IntPtr pointer, IntPtr clientData) { - var bytes = new List(Encoding.UTF8.GetBytes(Value)); - bytes.Add(0); /* Append the null-terminated character. */ - Marshal.Copy(bytes.ToArray(), 0, pointer, bytes.Count); + Helpers.CopyStrToPtr(pointer, Value); } public override String ToString() diff --git a/Library/Types.cs b/Library/Types.cs index 500a153..7699798 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -57,22 +57,37 @@ public static partial class Tw /// /// Called by AntTweakBar when the user changes a variable's value. /// - public delegate void SetVarCallback([In] IntPtr value, [In] IntPtr clientData); + public delegate void SetVarCallback( + [In] IntPtr value, + [In] IntPtr clientData); /// /// Called by AntTweakBar when AntTweakBar needs a variable's value. /// - public delegate void GetVarCallback([In] IntPtr value, [In] IntPtr clientData); + public delegate void GetVarCallback( + [In] IntPtr value, + [In] IntPtr clientData); /// /// Called by AntTweakBar when the user clicks on a button. /// - public delegate void ButtonCallback([In] IntPtr clientData); + public delegate void ButtonCallback( + [In] IntPtr clientData); + + /// + /// Called by AntTweakBar to retrieve a summary of a struct variable. + /// + public delegate void SummaryCallback( + [In] IntPtr summaryString, + [In] IntPtr summaryMaxLength, + [In] IntPtr value, + [In] IntPtr clientData); /// /// Called by AntTweakBar when an error occurs. /// - public delegate void ErrorHandler([In] IntPtr message); + public delegate void ErrorHandler( + [In] IntPtr message); /// /// Specifies the graphics API's AntTweakBar supports. diff --git a/README.md b/README.md index d4fd555..7efad3f 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,10 @@ Any issues or pull requests are welcome, I especially need help with verifying m * test multi-window support exhaustively * check it works on OS X * finish adding all low-level functions (even those we don't use, like the struct stuff) for completeness + currently missing: + - wrapper function to TwDefineStruct (this one will be tricky) + - string copy stuff (ignore, we don't need and never will) + - GLFW/GLUT callbacks (maybe...) * add extensions (in a separate assembly) to help interoperate with popular frameworks? (maybe) Changelog @@ -129,6 +133,7 @@ Next release: - changed `Tw.WindowSize` to accept sizes of (0, 0) to allow AntTweakBar resource cleanup (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) - added `ReleaseResources` and `ResetResources` methods to the `Context` class (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) - changed Sample to use GLSL version 120, fixed shader saving overwrite bug and close on Esc. + - added TwDefineEnum and TwDefineStruct native functions and a DefineEnum low-level wrapper 28 November 2014 (v0.4.4) From 8a09e64775b14978b00f972929c5b5f780410941 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 19:22:08 +1300 Subject: [PATCH 02/38] Fixed regression from 3f8df7a5373caac0816f6bb21ad5fd64af677ab9 (uninitialized shader variable) --- Sample/Shader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sample/Shader.cs b/Sample/Shader.cs index e12d2f1..7e71ab6 100644 --- a/Sample/Shader.cs +++ b/Sample/Shader.cs @@ -146,7 +146,7 @@ private static String FragIterate() str.AppendLine(); str.AppendLine("vec4 iterate(vec2 z)"); str.AppendLine("{"); - str.AppendLine(" float speed;"); + str.AppendLine(" float speed = 0;"); str.AppendLine(" int t;"); str.AppendLine(); str.AppendLine(" for (t = int(0); t < iters; ++t)"); @@ -207,7 +207,7 @@ private static String FragShade() str.AppendLine("vec3 shade(vec2 z)"); str.AppendLine("{"); str.AppendLine(" vec4 r = iterate(z);"); - str.AppendLine(" return colorize(z, r.xy, pow(r.z, intensity), r.w * r.z / iters);"); + str.AppendLine(" return colorize(z, r.xy, pow(r.z, intensity), r.w * intensity / iters);"); str.AppendLine("}"); return str.ToString(); From fe4132aa438d5fe47e6eb6b570b4a150352b56ec Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 19:34:17 +1300 Subject: [PATCH 03/38] Added convergence threshold variable in Sample. --- Sample/Fractal.cs | 12 ++++++++++++ Sample/Program.cs | 7 ++++++- Sample/Shader.cs | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Sample/Fractal.cs b/Sample/Fractal.cs index a696170..4e4885c 100644 --- a/Sample/Fractal.cs +++ b/Sample/Fractal.cs @@ -62,6 +62,16 @@ public int Iterations set { GL.Uniform1(GL.GetUniformLocation(shHandle, "iters"), iterations = value); } } + private float threshold; + public float Threshold + { + get { return threshold; } + set { + threshold = value; + GL.Uniform1(GL.GetUniformLocation(shHandle, "threshold"), (float)Math.Pow(10, -threshold)); + } + } + private bool hardcodePolynomial; public bool HardcodePolynomial { @@ -158,6 +168,7 @@ private void SetShaderVariables() KCoeff = kCoeff; Iterations = iterations; + Threshold = threshold; Palette = palette; if (!hardcodePolynomial) @@ -277,6 +288,7 @@ private void SetupOptions() hardcodePolynomial = true; intensity = 1; + threshold = 3; palette = Color4.Red; shading = ShadingType.Standard; diff --git a/Sample/Program.cs b/Sample/Program.cs index ed56676..616f84c 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -325,6 +325,11 @@ protected override void OnLoad(EventArgs _) configsBar.Label = "Configuration"; configsBar.Contained = true; + var thresholdVar = new FloatVariable(configsBar, fractal.Threshold); + thresholdVar.Changed += delegate { fractal.Threshold = thresholdVar.Value; }; + thresholdVar.SetDefinition("min=0 max=5 step=0.01 precision=2)"); + thresholdVar.Label = "Convergence"; + var itersVar = new IntVariable(configsBar, fractal.Iterations); itersVar.Changed += delegate { fractal.Iterations = itersVar.Value; }; itersVar.Label = "Iterations"; @@ -354,7 +359,7 @@ protected override void OnLoad(EventArgs _) var intensityVar = new FloatVariable(configsBar, fractal.Intensity); intensityVar.Changed += delegate { fractal.Intensity = intensityVar.Value; }; - intensityVar.SetDefinition("min=0 max=3 step=0.01 precision=3)"); + intensityVar.SetDefinition("min=0 max=3 step=0.01 precision=2)"); intensityVar.Label = "Intensity"; var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); diff --git a/Sample/Shader.cs b/Sample/Shader.cs index 7e71ab6..5d54495 100644 --- a/Sample/Shader.cs +++ b/Sample/Shader.cs @@ -143,6 +143,7 @@ private static String FragIterate() str.AppendLine("uniform vec2 aCoeff;"); str.AppendLine("uniform vec2 kCoeff;"); str.AppendLine("uniform int iters;"); + str.AppendLine("uniform float threshold;"); str.AppendLine(); str.AppendLine("vec4 iterate(vec2 z)"); str.AppendLine("{"); @@ -155,7 +156,7 @@ private static String FragIterate() str.AppendLine(" z -= cmul(cdiv(poly(z), derv(z)), aCoeff) + kCoeff;"); str.AppendLine(" float l = csqrabs(r - z);"); str.AppendLine(" speed += exp(-inversesqrt(l));"); - str.AppendLine(" if (l < 1e-8) break;"); + str.AppendLine(" if (l <= threshold) break;"); str.AppendLine(" }"); str.AppendLine(); str.AppendLine(" return vec4(z, speed, float(t));"); From 112efa9fe3955a6177f290b8570b1212feddfafc Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 19:51:27 +1300 Subject: [PATCH 04/38] Added event handling notes to readme. --- README.md | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7efad3f..d3d2aaa 100644 --- a/README.md +++ b/README.md @@ -25,35 +25,41 @@ The AntTweakBar.NET high-level interface is divided into three main concepts: co The first context created should be passed the graphics API you are using, which is some version of OpenGL or DirectX. For DirectX, you must also pass a pointer to the native device, which should be available from the graphics framework you are using somehow (for instance, for SharpDX, use `SharpDX.Direct3D11.Device.NativePointer`). - using AntTweakBar; +```csharp +using AntTweakBar; - /* ... */ +/* ... */ - context = new Context(Tw.GraphicsAPI.Direct3D11, /* pointer to device */); +context = new Context(Tw.GraphicsAPI.Direct3D11, /* pointer to device */); +``` Other contexts do not have to provide a graphics API, and can be created as simply `new Context();`. AntTweakBar.NET keeps track of how many contexts are active, and initializes the AntTweakBar library whenever a first one is created, and terminates the library whenever the last one is destroyed. Once you have a context, you can create bars inside it, and you can create variables inside these bars. To draw the context, call its `Draw()` method at the very end of your rendering pipeline. To handle events, hook up the various `Handle*()` methods to your window events. Keep in mind that you generally do not need to keep references to variables around. In many cases, it is sufficient to set up a delegate on the variable's `Changed` event to automatically modify some property in another class, so that your program automatically responds to variable changes. - var myBar = new Bar(context); - myBar.Label = "Some bar"; - myBar.Contained = true; // set some bar properties +```csharp +var myBar = new Bar(context); +myBar.Label = "Some bar"; +myBar.Contained = true; // set some bar properties - var rotationVar = new IntVariable(myBar, 42 /* default value */); - rotationVar.Label = "Model rotation"; - rotationVar.Changed += delegate { model.Rotation = rotationVar.Value; }; +var rotationVar = new IntVariable(myBar, 42 /* default value */); +rotationVar.Label = "Model rotation"; +rotationVar.Changed += delegate { model.Rotation = rotationVar.Value; }; - /* don't need rotationVar anymore (it will still be held onto by myBar) */ +/* don't need rotationVar anymore (it will still be held onto by myBar) */ +``` Generic event handling example to illustrate (this is not the only event you need to handle): - protected override void OnResize(EventArgs e) - { - base.OnResize(e); - context.HandleResize(this.ClientSize); - } +```csharp +protected override void OnResize(EventArgs e) +{ + base.OnResize(e); + context.HandleResize(this.ClientSize); +} +``` -The preferred way of doing event handling is by using the `Handle*()` events, which means you have to do some event translation. However, if you are using a particular framework, it may be possible to use ready-made event handlers. For instance, if you are using WinForms and happen to have access to your form's `WndProc` (perhaps because you are already overriding it) then you can use `EventHandlerWin()` to handle all events except perhaps `HandleResize` in a single line of code. There is currently such support for WinForms, SFML (via SFML.Net) and SDL (untested). Using the generic handlers works anywhere, though. +The preferred way of doing event handling is by using the `Handle*()` events, which means you have to do some event translation. However, if you are using a particular framework, it may be possible to use ready-made event handlers. For instance, if you are using WinForms and happen to have access to your form's `WndProc` (perhaps because you are already overriding it) then you can use `EventHandlerWin()` to handle all events except perhaps `HandleResize` in a single line of code. There is currently such support for WinForms, SFML (via SFML.Net), X11 events and SDL (untested). The other two handlers supported by AntTweakBar (GLFW and GLUT) use per-event callbacks, so it probably does not make much sense wrapping them as your respective GLFW or GLUT wrapper should already convert them into events or delegates for you. Using the generic handlers works anywhere, though. In general you *do* want to keep references to contexts, because you actually do need to destroy them when you close your windows. The different AntTweakBar.NET classes implement the `IDisposable` interface. When you dispose a bar, all variables inside it are implicitly disposed. When you dispose a context, all bars inside it are implicitly disposed. In other words, it is sufficient to dispose the contexts you create. It is very important to note that you must dispose the last context **before** terminating your graphics API. A symptom of failing to do this is an exception on shutdown pointing to the `Tw.Terminate()` function. Critically, this means you cannot just leave the contexts to be garbage-collected, as it will probably be too late by the time they are. This should not be a problem in most sensible implementations. @@ -73,8 +79,10 @@ Advanced Usage You can script the different properties of your bars or variables from e.g. a text file using the `SetDefinition` method. This method takes a definition string containing your parameters. For your convenience, there is an optional `def` parameter in the constructor which automatically calls it as well. This method should be used e.g. as: - myVariable.SetDefinition("label='New Label' readonly=true"); - myBar.SetDefinition("contained=true visible=false"); + ```csharp + myVariable.SetDefinition("label='New Label' readonly=true"); + myBar.SetDefinition("contained=true visible=false"); + ``` This definition string follows the same format as documented on the AntTweakBar website under `TwDefine`, except it should not contain the variable's name, as you don't know what it is (it is automatically filled in by the method). @@ -84,7 +92,9 @@ Advanced Usage If user input fails validation, the variable will gracefully revert to its previous value. On the other hand, if you manually try to set an invalid value from code, it will throw an `ArgumentException`. The following example shows how to make the value of `myVar` be a multiple of five: - myVar.Validating += (s, e) => { e.Valid = (e.Value % 5 == 0); }; /* for IntVariable */ + ```csharp + myVar.Validating += (s, e) => { e.Valid = (e.Value % 5 == 0); }; /* for IntVariable */ + ``` You must refer to `e.Value` (or `e.R`, `e.X`, etc. as appropriate) to perform validation, as the variable's value has not yet been updated when the validation handlers are called. Note you can of course refer to external objects in your handler to implement context-sensitive validation logic. Most variables already have built-in validators, for instance numeric variables validate against their `Min` and `Max` properties, `StringVariable` rejects null strings, etc. From 45bcc8f4b05f1ee15fe525abc9d14a1b0d5a04e8 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 20:07:00 +1300 Subject: [PATCH 05/38] Added TwKeyTest stuff (+ Context functions). --- Library/Context.cs | 21 +++++++++++++++++++++ Library/Native.cs | 17 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Library/Context.cs b/Library/Context.cs index faee4b8..0694062 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -149,6 +149,16 @@ public bool HandleKeyPress(char key) return Tw.KeyPressed((int)key, Tw.KeyModifier.None); } + /// + /// Tests this context for whether a key press would be handled. + /// + /// The key character pressed. + public bool HandleKeyTest(char key) + { + Tw.SetCurrentWindow(Identifier); + return Tw.KeyTest((int)key, Tw.KeyModifier.None); + } + /// /// Notifies this context of a special key press. /// @@ -160,6 +170,17 @@ public bool HandleKeyPress(Tw.SpecialKey key, Tw.KeyModifier modifiers) return Tw.KeyPressed((int)key, modifiers); } + /// + /// Tests this context for whether a special key press would be handled. + /// + /// The key pressed. + /// The key modifiers pressed. + public bool HandleKeyTest(Tw.SpecialKey key, Tw.KeyModifier modifiers) + { + Tw.SetCurrentWindow(Identifier); + return Tw.KeyTest((int)key, modifiers); + } + /// /// The SFML event handler. /// diff --git a/Library/Native.cs b/Library/Native.cs index 40fd336..2cd027a 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -64,6 +64,12 @@ internal static class NativeMethods [In] Int32 key, [In] Tw.KeyModifier modifiers); + [DllImport(DLLName, EntryPoint = "TwKeyTest")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern Boolean TwKeyTest( + [In] Int32 key, + [In] Tw.KeyModifier modifiers); + [DllImport(DLLName, EntryPoint = "TwEventSFML")] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean TwEventSFML( @@ -452,6 +458,17 @@ public static bool KeyPressed(int key, KeyModifier modifiers) return NativeMethods.TwKeyPressed(key, modifiers); } + /// + /// This function checks if a key event would be processed but without processing it. This could be helpful to prevent bad handling report. + /// + /// The ASCII code of the pressed key, or one of the codes. + /// One or a combination of the constants. + /// Whether the key event would have been handled by AntTweakBar. + public static bool KeyTest(int key, KeyModifier modifiers) + { + return NativeMethods.TwKeyTest(key, modifiers); + } + /// /// The SFML event handler. /// From d360967ea460b7e4134af93e8790018d1ba9f6c0 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 20:13:53 +1300 Subject: [PATCH 06/38] Hardcoded iteration and convergence threshold in sample. --- Sample/Fractal.cs | 15 +++++++++------ Sample/Shader.cs | 18 ++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Sample/Fractal.cs b/Sample/Fractal.cs index 4e4885c..7f1ad67 100644 --- a/Sample/Fractal.cs +++ b/Sample/Fractal.cs @@ -59,16 +59,21 @@ public Size Dimensions public int Iterations { get { return iterations; } - set { GL.Uniform1(GL.GetUniformLocation(shHandle, "iters"), iterations = value); } + set + { + iterations = value; + SetupShaders(); + } } private float threshold; public float Threshold { get { return threshold; } - set { + set + { threshold = value; - GL.Uniform1(GL.GetUniformLocation(shHandle, "threshold"), (float)Math.Pow(10, -threshold)); + SetupShaders(); } } @@ -167,8 +172,6 @@ private void SetShaderVariables() ACoeff = aCoeff; KCoeff = kCoeff; - Iterations = iterations; - Threshold = threshold; Palette = palette; if (!hardcodePolynomial) @@ -236,7 +239,7 @@ private void CreateShaders() fsHandle = GL.CreateShader(ShaderType.FragmentShader); GL.ShaderSource(vsHandle, Shader.VertShader()); - GL.ShaderSource(fsHandle, Shader.FragShader(polynomial, shading, aa, hardcodePolynomial)); + GL.ShaderSource(fsHandle, Shader.FragShader(polynomial, shading, aa, hardcodePolynomial, iterations, threshold)); GL.CompileShader(vsHandle); GL.CompileShader(fsHandle); diff --git a/Sample/Shader.cs b/Sample/Shader.cs index 5d54495..d9748bf 100644 --- a/Sample/Shader.cs +++ b/Sample/Shader.cs @@ -37,7 +37,7 @@ public static String VertShader() /// /// Gets the fragment shader. /// - public static String FragShader(Polynomial poly, ShadingType type, AAQuality aa, bool hardcodePoly) + public static String FragShader(Polynomial poly, ShadingType type, AAQuality aa, bool hardcodePoly, int iterations, float threshold) { var shader = String.Join("\n", new[] { @@ -46,9 +46,9 @@ public static String FragShader(Polynomial poly, ShadingType type, AAQuality aa, FragArithmetic(), FragPolyRoots(poly, "poly", hardcodePoly), FragPolyRoots(Polynomial.Derivative(poly), "derv", hardcodePoly), - FragIterate(), + FragIterate(iterations, threshold), FragColorize(type), - FragShade(), + FragShade(iterations), FragMainSampler(aa) }); @@ -136,27 +136,25 @@ private static String FragPolyRoots(Polynomial poly, String name, bool hardcode return str.ToString(); } - private static String FragIterate() + private static String FragIterate(int iterations, float threshold) { var str = new StringBuilder(); str.AppendLine("uniform vec2 aCoeff;"); str.AppendLine("uniform vec2 kCoeff;"); - str.AppendLine("uniform int iters;"); - str.AppendLine("uniform float threshold;"); str.AppendLine(); str.AppendLine("vec4 iterate(vec2 z)"); str.AppendLine("{"); str.AppendLine(" float speed = 0;"); str.AppendLine(" int t;"); str.AppendLine(); - str.AppendLine(" for (t = int(0); t < iters; ++t)"); + str.AppendLine(" for (t = int(0); t < " + iterations + "; ++t)"); str.AppendLine(" {"); str.AppendLine(" vec2 r = z;"); str.AppendLine(" z -= cmul(cdiv(poly(z), derv(z)), aCoeff) + kCoeff;"); str.AppendLine(" float l = csqrabs(r - z);"); str.AppendLine(" speed += exp(-inversesqrt(l));"); - str.AppendLine(" if (l <= threshold) break;"); + str.AppendLine(" if (l <= " + Math.Pow(10, -threshold) + ") break;"); str.AppendLine(" }"); str.AppendLine(); str.AppendLine(" return vec4(z, speed, float(t));"); @@ -199,7 +197,7 @@ private static String FragColorize(ShadingType type) return str.ToString(); } - private static String FragShade() + private static String FragShade(int iterations) { var str = new StringBuilder(); @@ -208,7 +206,7 @@ private static String FragShade() str.AppendLine("vec3 shade(vec2 z)"); str.AppendLine("{"); str.AppendLine(" vec4 r = iterate(z);"); - str.AppendLine(" return colorize(z, r.xy, pow(r.z, intensity), r.w * intensity / iters);"); + str.AppendLine(" return colorize(z, r.xy, pow(r.z, intensity), r.w * intensity / " + iterations + ");"); str.AppendLine("}"); return str.ToString(); From 52c71954a77d4789873b5a870a27f2a5ba99f1a8 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 20:15:25 +1300 Subject: [PATCH 07/38] Updated readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d3d2aaa..73f314c 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Next release: - added `ReleaseResources` and `ResetResources` methods to the `Context` class (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) - changed Sample to use GLSL version 120, fixed shader saving overwrite bug and close on Esc. - added TwDefineEnum and TwDefineStruct native functions and a DefineEnum low-level wrapper + - various miscellaneous fixes and improvements to the Sample 28 November 2014 (v0.4.4) From f61d9fd3c17a7c191d7930bd2632e388ccb088ac Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 20:51:41 +1300 Subject: [PATCH 08/38] Added (untested) wrapper for TwDefineStruct. --- Library/EnumVariable.cs | 2 +- Library/Native.cs | 64 +++++++++++++++++++++++++++++++++++++++++ Library/Types.cs | 48 ++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/Library/EnumVariable.cs b/Library/EnumVariable.cs index 93a0c32..229b2e8 100644 --- a/Library/EnumVariable.cs +++ b/Library/EnumVariable.cs @@ -75,7 +75,7 @@ private static void InitEnumVariable(Variable var, String id) Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); Tw.AddVarCB(var.ParentBar.Pointer, id, - Tw.DefineEnum(Guid.NewGuid().ToString(), GetEnumLabels()), + Tw.DefineEnum(typeof(T).FullName, GetEnumLabels()), Variable.SetCallbacks[id], Variable.GetCallbacks[id], IntPtr.Zero, null); diff --git a/Library/Native.cs b/Library/Native.cs index 2cd027a..a3622cb 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -1171,6 +1171,70 @@ public static VariableType DefineEnumFromString(String name, String enumString) return enumType; } + /// + /// This function creates a new corresponding to a structure. + /// + public static VariableType DefineStruct(String name, IDictionary structMembers, int structSize, Tw.SummaryCallback callback, IntPtr clientData) + { + if (name == null) { + throw new ArgumentNullException("name"); + } else if (structMembers == null) { + throw new ArgumentNullException("structMembers"); + } else if (structSize == 0) { + throw new ArgumentOutOfRangeException("structSize"); + } + + var structData = new List(); + + try + { + foreach (var kv in structMembers) { + IntPtr namePtr = IntPtr.Zero; + IntPtr defPtr = IntPtr.Zero; + + try + { + namePtr = Helpers.PtrFromStr(kv.Key); + defPtr = Helpers.PtrFromStr(kv.Value.Def); + + structData.Add(new NativeMethods.StructMember( + namePtr, + kv.Value.Type, + new UIntPtr((uint)kv.Value.Offset), + defPtr + )); + } + catch (Exception) + { + if (namePtr != IntPtr.Zero) { + Marshal.FreeCoTaskMem(namePtr); + } + + if (defPtr != IntPtr.Zero) { + Marshal.FreeCoTaskMem(defPtr); + } + + throw; + } + } + + Tw.VariableType structType = NativeMethods.TwDefineStruct(name, structData.ToArray(), (uint)structData.Count, new UIntPtr((uint)structSize), callback, clientData); + + if (structType == VariableType.Undefined) { + throw new AntTweakBarException("TwDefineStruct failed."); + } + + return structType; + } + finally + { + foreach (var member in structData) { + Marshal.FreeCoTaskMem(member.Name); + Marshal.FreeCoTaskMem(member.DefString); + } + } + } + /// /// This function removes a variable, button or separator from a tweak bar. /// diff --git a/Library/Types.cs b/Library/Types.cs index 7699798..ae333ba 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; namespace AntTweakBar @@ -149,7 +150,7 @@ public enum ParamValueType internal const int MaxStringLength = 4096; /// - /// Specifies the different possible variable type, excluding enums. + /// Specifies the different possible variable type, excluding enums and structs. /// public enum VariableType { @@ -243,6 +244,51 @@ public enum VariableType CSString = 0x30000000 + MaxStringLength, }; + /// + /// Specifies information about a member of an AntTweakBar struct variable. + /// + public struct StructMemberInfo + { + /// + /// Gets the AntTweakBar type of this member. + /// + public VariableType Type { get { return type; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly VariableType type; + + /// + /// Gets the offset in bytes of this member. + /// + public int Offset { get { return offset; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly int offset; + + /// + /// Gets the definition string to use for this member. + /// + public String Def { get { return def; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly String def; + + /// + /// Initializes a new instance of the struct. + /// + /// The struct member's AntTweakBar type. + /// The struct member's offset in bytes. + /// A definition string for the struct member. + public StructMemberInfo(VariableType type, int offset, String def) : this() + { + this.type = type; + this.offset = offset; + this.def = def; + } + + public override String ToString() + { + return String.Format("[StructMemberInfo: Type={0}, Offset={1}, Def={2}]", Type, Offset, Def); + } + } + /// /// Specifies the possible mouse actions recognized by AntTweakBar. /// From 8f9dcb0b18b3af2b57d928d57155e39d5f0d7a6f Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 21:08:10 +1300 Subject: [PATCH 09/38] Added unfinished StructVariable (needs thought). --- Library/Library.csproj | 3 +- Library/StructVariable.cs | 153 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 Library/StructVariable.cs diff --git a/Library/Library.csproj b/Library/Library.csproj index f157ea3..3c62f49 100644 --- a/Library/Library.csproj +++ b/Library/Library.csproj @@ -29,7 +29,6 @@ DEBUG; bin\Debug 4 - false 1591 bin\Debug\AntTweakBar.NET.xml ExtendedCorrectnessRules.ruleset @@ -38,7 +37,6 @@ true bin\Release 4 - false true 1591 bin\Release\AntTweakBar.NET.xml @@ -69,6 +67,7 @@ + \ No newline at end of file diff --git a/Library/StructVariable.cs b/Library/StructVariable.cs new file mode 100644 index 0000000..171044d --- /dev/null +++ b/Library/StructVariable.cs @@ -0,0 +1,153 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace AntTweakBar +{ + public sealed class StructValidationEventArgs : EventArgs where T : struct + { + /// + /// Whether to accept this integer value. + /// + public bool Valid { get; set; } + public T Value { get; private set; } + + public StructValidationEventArgs(T value) + { + Value = value; + } + } + + /// + /// An AntTweakBar variable which can hold a composite struct. + /// + public sealed class StructVariable : Variable where T : struct + { + /// + /// Occurs when the user changes this variable's value. + /// + public event EventHandler Changed; + + /// + /// Occurs when the new value of this variable is validated. + /// + public event EventHandler> Validating; + + /// + /// Raises the Changed event. + /// + public void OnChanged(EventArgs e) + { + ThrowIfDisposed(); + + if (Changed != null) { + Changed(this, e); + } + } + + /// + /// Gets or sets the value of this variable. + /// + public T Value + { + get { ThrowIfDisposed(); return value; } + set { ValidateAndSet(value); } + } + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private T value; + + /// + /// Initialization delegate, which creates the struct variable. + /// + private static void InitStructVariable(Variable var, String id) + { + if (!(typeof(T).IsValueType && !typeof(T).IsPrimitive && !typeof(T).IsEnum)) { + throw new ArgumentException(String.Format("Type {0} is not a struct.", typeof(T).FullName)); + } + + var it = var as StructVariable; + + Variable.SetCallbacks.Add(id, new Tw.SetVarCallback(it.SetCallback)); + Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); + + // here parse the struct into a StructMemberInfo dict and use Tw.DefineStruct + // how to parse it? what if we want to exclude some members? custom summary? + + throw new NotImplementedException("Not finished yet!"); + } + + /// + /// Creates a new struct variable in a given bar. + /// + /// The bar to create the struct variable in. + /// The initial value of the variable. + /// An optional definition string for the new variable. + public StructVariable(Bar bar, T initialValue = default(T), String def = null) + : base(bar, InitStructVariable, def) + { + Validating += (s, e) => { e.Valid = true; }; + + ValidateAndSet(initialValue); + } + + /// + /// Checks if this variable can hold this value. + /// + private bool IsValid(T value) + { + ThrowIfDisposed(); + + return !Validating.GetInvocationList().Select(h => { + var check = new StructValidationEventArgs(value); + h.DynamicInvoke(new object[] { this, check }); + return !check.Valid; + }).Any(failed => failed); + } + + /// + /// Tries to set this variable's value, validating it. + /// + private void ValidateAndSet(T value) + { + if (!IsValid(value)) { + throw new ArgumentException("Invalid variable value."); + } else { + this.value = value; + } + } + + /// + /// Called by AntTweakBar when the user changes the variable's value. + /// + private void SetCallback(IntPtr pointer, IntPtr clientData) + { + T data = (T)Marshal.PtrToStructure(pointer, typeof(T)); + + if (IsValid(data)) + { + bool changed = !value.Equals(data); + value = data; + + if (changed) { + OnChanged(EventArgs.Empty); + } + } + } + + /// + /// Called by AntTweakBar when AntTweakBar needs the variable's value. + /// + private void GetCallback(IntPtr pointer, IntPtr clientData) + { + Marshal.StructureToPtr(Value, pointer, false); + } + + public override String ToString() + { + return String.Format("[StructVariable: Label={0}, Value={1}]", Label, Value); + } + } +} + From 33b8e5d4d034f52dacf9e073e9e9a52d3fa454da Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 21:16:11 +1300 Subject: [PATCH 10/38] Updated readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73f314c..2f13909 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Advanced Usage - **AntTweakBar bindings** - The bindings are in the `AntTweakBar.Tw` static class. You (mostly) cannot interfere with the wrapper's operation with them since the wrapper does not expose its internal AntTweakBar pointers and identifiers for integrity reasons, so it is discouraged to use them to try and subvert the high-level wrapper. You are however encouraged to use them directly if you don't want to or can't use the high-level classes for whatever reason. Have fun! + The bindings are in the `AntTweakBar.Tw` static class. For the most part you cannot interfere with the wrapper's operation with them since the wrapper does not expose its internal AntTweakBar pointers and identifiers for integrity reasons, so it is discouraged to use them to try and subvert the high-level wrapper. You are however encouraged to use them directly if you don't want to or can't use the high-level classes for whatever reason. Have fun! - **Exception safety** From c9c42140bd6707c9c45fd8bab23d7637491dcfbc Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 21:16:11 +1300 Subject: [PATCH 11/38] Updated readme. --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 73f314c..02ad383 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Advanced Usage - **AntTweakBar bindings** - The bindings are in the `AntTweakBar.Tw` static class. You (mostly) cannot interfere with the wrapper's operation with them since the wrapper does not expose its internal AntTweakBar pointers and identifiers for integrity reasons, so it is discouraged to use them to try and subvert the high-level wrapper. You are however encouraged to use them directly if you don't want to or can't use the high-level classes for whatever reason. Have fun! + The bindings are in the `AntTweakBar.Tw` static class. For the most part you cannot interfere with the wrapper's operation with them since the wrapper does not expose its internal AntTweakBar pointers and identifiers for integrity reasons, so it is discouraged to use them to try and subvert the high-level wrapper. You are however encouraged to use them directly if you don't want to or can't use the high-level classes for whatever reason. Have fun! - **Exception safety** @@ -127,12 +127,8 @@ Any issues or pull requests are welcome, I especially need help with verifying m * more/better unit tests * test multi-window support exhaustively + * thread-safety concerns? * check it works on OS X - * finish adding all low-level functions (even those we don't use, like the struct stuff) for completeness - currently missing: - - wrapper function to TwDefineStruct (this one will be tricky) - - string copy stuff (ignore, we don't need and never will) - - GLFW/GLUT callbacks (maybe...) * add extensions (in a separate assembly) to help interoperate with popular frameworks? (maybe) Changelog From 3f3f38b040270b89076a656497a8f64b88eb16f4 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 22:05:24 +1300 Subject: [PATCH 12/38] More work on struct variables. --- Library/EnumVariable.cs | 9 ++++++++- Library/Native.cs | 13 ++++++++++++- Library/StructVariable.cs | 24 +++++++++++++++++------- Library/Types.cs | 14 +++++++++++++- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Library/EnumVariable.cs b/Library/EnumVariable.cs index 229b2e8..8d0caa3 100644 --- a/Library/EnumVariable.cs +++ b/Library/EnumVariable.cs @@ -60,6 +60,13 @@ public T Value [DebuggerBrowsable(DebuggerBrowsableState.Never)] private T value; + /// + /// Gets this enum variable's type. + /// + public Tw.VariableType Type { get { ThrowIfDisposed(); return type; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Tw.VariableType type; + /// /// Initialization delegate, which creates the enum variable. /// @@ -75,7 +82,7 @@ private static void InitEnumVariable(Variable var, String id) Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); Tw.AddVarCB(var.ParentBar.Pointer, id, - Tw.DefineEnum(typeof(T).FullName, GetEnumLabels()), + it.type = Tw.DefineEnum(Guid.NewGuid().ToString(), GetEnumLabels()), Variable.SetCallbacks[id], Variable.GetCallbacks[id], IntPtr.Zero, null); diff --git a/Library/Native.cs b/Library/Native.cs index a3622cb..f7169e9 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -1192,6 +1192,14 @@ public static VariableType DefineStruct(String name, IDictionary + /// Gets this struct variable's type. + /// + public Tw.VariableType Type { get { ThrowIfDisposed(); return type; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Tw.VariableType type; + /// /// Initialization delegate, which creates the struct variable. /// - private static void InitStructVariable(Variable var, String id) + private static void InitStructVariable(Variable var, String id, IDictionary members) { if (!(typeof(T).IsValueType && !typeof(T).IsPrimitive && !typeof(T).IsEnum)) { throw new ArgumentException(String.Format("Type {0} is not a struct.", typeof(T).FullName)); @@ -72,20 +80,22 @@ private static void InitStructVariable(Variable var, String id) Variable.SetCallbacks.Add(id, new Tw.SetVarCallback(it.SetCallback)); Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); - // here parse the struct into a StructMemberInfo dict and use Tw.DefineStruct - // how to parse it? what if we want to exclude some members? custom summary? - - throw new NotImplementedException("Not finished yet!"); + Tw.AddVarCB(var.ParentBar.Pointer, id, + it.type = Tw.DefineStruct(Guid.NewGuid().ToString(), members, (int)Marshal.SizeOf(typeof(T)), null, IntPtr.Zero), + Variable.SetCallbacks[id], + Variable.GetCallbacks[id], + IntPtr.Zero, null); } /// /// Creates a new struct variable in a given bar. /// /// The bar to create the struct variable in. + /// The set of struct members to consider. /// The initial value of the variable. /// An optional definition string for the new variable. - public StructVariable(Bar bar, T initialValue = default(T), String def = null) - : base(bar, InitStructVariable, def) + public StructVariable(Bar bar, IDictionary members, T initialValue = default(T), String def = null) + : base(bar, (var, id) => InitStructVariable(var, id, members), def) { Validating += (s, e) => { e.Valid = true; }; diff --git a/Library/Types.cs b/Library/Types.cs index ae333ba..edfa6d0 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -276,13 +276,25 @@ public struct StructMemberInfo /// The struct member's AntTweakBar type. /// The struct member's offset in bytes. /// A definition string for the struct member. - public StructMemberInfo(VariableType type, int offset, String def) : this() + public StructMemberInfo(VariableType type, int offset, String def = "") : this() { + if (def == null) { + def = ""; + } + this.type = type; this.offset = offset; this.def = def; } + /// + /// Creates a suitable StructMemberInfo from a struct member name. + /// + public static StructMemberInfo FromStruct(String member, VariableType type, String def = "") where T : struct + { + return new StructMemberInfo(type, (int)Marshal.OffsetOf(typeof(T), member), def); + } + public override String ToString() { return String.Format("[StructMemberInfo: Type={0}, Offset={1}, Def={2}]", Type, Offset, Def); From 5a80ba972d8471cbc1e6b8b9ae3064bc1275d894 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 22:13:13 +1300 Subject: [PATCH 13/38] Proof of concept implementation for complex numbers... not sure if this is any better, really. --- Sample/Program.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Sample/Program.cs b/Sample/Program.cs index 616f84c..b9dc0e6 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -362,11 +362,18 @@ protected override void OnLoad(EventArgs _) intensityVar.SetDefinition("min=0 max=3 step=0.01 precision=2)"); intensityVar.Label = "Intensity"; - var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); + /*var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; aCoeffVar.Label = "Relaxation Coeff."; aCoeffVar.Step = 0.0002; - aCoeffVar.Precision = 4; + aCoeffVar.Precision = 4;*/ + + var aCoeffVar = new StructVariable(configsBar, new Dictionary() + { + { "re", Tw.StructMemberInfo.FromStruct("real", Tw.VariableType.Double, "label='real' step=0.0002 precision=4") }, + { "im", Tw.StructMemberInfo.FromStruct("imaginary", Tw.VariableType.Double, "label='imaginary' step=0.0002 precision=4") }, + }, fractal.ACoeff, "label='Relaxation Coeff.'"); + aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; var kcoeff = new ComplexVariable(configsBar, fractal.KCoeff); kcoeff.Changed += delegate { fractal.KCoeff = kcoeff.Value; }; From c367c804afd5e113763534276d7ee70b86a6a766 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 22:31:14 +1300 Subject: [PATCH 14/38] Added thread safety and multiple windows notes in readme. --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02ad383..d6dd98a 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,10 @@ Advanced Usage By default an `EnumVariable` will graphically display the name of the enum value as defined in your code. You can give it a custom name or summary by tagging your enum values with a `DescriptionAttribute`, it will be picked up by AntTweakBar.NET and displayed instead of the raw enum name. + - **Multiple windows** + + Each different window you want AntTweakBar to use should have a `Context`, and you should handle events and call the context's `Draw` method as needed on each window. That's really all there is to it. Internally, AntTweakBar directly draws into whatever render target is active, so multiple windows are naturally handled in both OpenGL and DirectX. The wrapper takes care of all necessary context-switching. + - **AntTweakBar bindings** The bindings are in the `AntTweakBar.Tw` static class. For the most part you cannot interfere with the wrapper's operation with them since the wrapper does not expose its internal AntTweakBar pointers and identifiers for integrity reasons, so it is discouraged to use them to try and subvert the high-level wrapper. You are however encouraged to use them directly if you don't want to or can't use the high-level classes for whatever reason. Have fun! @@ -118,6 +122,10 @@ Advanced Usage You should avoid throwing exceptions from within delegates subscribed to `Changed`, `Click` and `Validate` events. The reason for this is because they can be called from native code, and the results of throwing a managed exception inside an unmanaged callback is implementation-defined. It is recommended instead to log any exception and safely return. + - **Thread safety** + + Unfortunately, several AntTweakBar functions are **not** thread safe due to the global state involved in switching AntTweakBar windows/contexts. The only code which has been specifically written to be thread-safe is the constructor (and to some extent the `Dispose` method) of the `Context` class, in order to avoid double initialization/finalization of the AntTweakBar library. In general, assume no function is thread-safe, just like most GUI frameworks. + Contribute ---------- @@ -126,10 +134,7 @@ Any issues or pull requests are welcome, I especially need help with verifying m **Todo**: * more/better unit tests - * test multi-window support exhaustively - * thread-safety concerns? * check it works on OS X - * add extensions (in a separate assembly) to help interoperate with popular frameworks? (maybe) Changelog --------- From 37f21a13f6832fc44147c5cf015857db16a50306 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 2 Dec 2014 23:08:40 +1300 Subject: [PATCH 15/38] Reverted StructVariable usage in Sample.. as I said, not very good. --- Sample/Program.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Sample/Program.cs b/Sample/Program.cs index b9dc0e6..616f84c 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -362,18 +362,11 @@ protected override void OnLoad(EventArgs _) intensityVar.SetDefinition("min=0 max=3 step=0.01 precision=2)"); intensityVar.Label = "Intensity"; - /*var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); + var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; aCoeffVar.Label = "Relaxation Coeff."; aCoeffVar.Step = 0.0002; - aCoeffVar.Precision = 4;*/ - - var aCoeffVar = new StructVariable(configsBar, new Dictionary() - { - { "re", Tw.StructMemberInfo.FromStruct("real", Tw.VariableType.Double, "label='real' step=0.0002 precision=4") }, - { "im", Tw.StructMemberInfo.FromStruct("imaginary", Tw.VariableType.Double, "label='imaginary' step=0.0002 precision=4") }, - }, fractal.ACoeff, "label='Relaxation Coeff.'"); - aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; + aCoeffVar.Precision = 4; var kcoeff = new ComplexVariable(configsBar, fractal.KCoeff); kcoeff.Changed += delegate { fractal.KCoeff = kcoeff.Value; }; From a991d7e0cbb60a4002eac30492238d81bb81312d Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 3 Dec 2014 21:49:36 +1300 Subject: [PATCH 16/38] Added better group management (not finished, need to update sample). --- Library/Bar.cs | 22 ------ Library/Group.cs | 160 +++++++++++++++++++++++++++++++++++++++++ Library/Library.csproj | 1 + Library/Variable.cs | 26 ++++++- Sample/Program.cs | 28 +++++--- Tests/Tests.cs | 4 +- 6 files changed, 206 insertions(+), 35 deletions(-) create mode 100644 Library/Group.cs diff --git a/Library/Bar.cs b/Library/Bar.cs index 489fb7d..693e476 100644 --- a/Library/Bar.cs +++ b/Library/Bar.cs @@ -71,28 +71,6 @@ public void SetDefinition(String def) #region Customization - /// - /// Shows or hides a variable group in this bar. - /// - /// The name of the group to show or hide. - /// Whether the group should be visible. - public void ShowGroup(String group, Boolean visible) - { - Tw.SetCurrentWindow(ParentContext.Identifier); - Tw.Define(String.Format("{0}/`{1}` visible={2}", ID, group, visible ? "true" : "false")); - } - - /// - /// Opens or closes a variable group in this bar. - /// - /// The name of the group to open or close. - /// Whether the group should be open. - public void OpenGroup(String group, Boolean opened) - { - Tw.SetCurrentWindow(ParentContext.Identifier); - Tw.Define(String.Format("{0}/`{1}` opened={2}", ID, group, opened ? "true" : "false")); - } - /// /// Moves a variable group into another group. /// diff --git a/Library/Group.cs b/Library/Group.cs new file mode 100644 index 0000000..3d01e77 --- /dev/null +++ b/Library/Group.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace AntTweakBar +{ + /// + /// An AntTweakBar group, to hierarchically organize variables in bars. + /// + public sealed class Group : IEquatable + { + /// + /// Gets this group's parent bar. + /// + public Bar ParentBar { get { return parentBar; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Bar parentBar; + + /// + /// Gets this group's unique identifier. + /// + internal String ID { get { return id; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private String id; + + /// + /// Internal constructor used for reconstructing a group from an identifier. + /// + /// The parent bar of the new group. + /// The identifier of the new group. + internal Group(Bar bar, String id) + { + this.parentBar = bar; + this.id = id; + } + + /// + /// Creates a new group in a given bar and puts variables in it. + /// + /// The bar the new group should belong to. + /// A label to display for the new group. + /// Variables to put in the new group. + public Group(Bar parentBar, String label, params Variable[] variables) + { + if (parentBar == null) { + throw new ArgumentNullException("parentBar"); + } else if (label == null) { + throw new ArgumentNullException("label"); + } else if (variables == null) { + throw new ArgumentNullException("variables"); + } + + id = Guid.NewGuid().ToString(); + this.parentBar = parentBar; + + foreach (var variable in variables) { + variable.Group = this; + } + + Label = label; + } + + /// + /// Creates a new group in a given bar and puts variables in it. + /// + /// The bar the new group should belong to. + /// A label to display for the new group. + /// Variables to put in the new group. + public Group(Bar parentBar, String label, IEnumerable variables) + : this(parentBar, label, variables.ToArray()) + { + + } + + /// + /// Gets or sets this group's label. + /// + public String Label + { + get { return Tw.GetStringParam(ParentBar.Pointer, ID, "label"); } + set { Tw.SetParam(ParentBar.Pointer, ID, "label", value); } + } + + /// + /// Gets the sets the parent group this group is in. + /// + public Group Parent + { + get + { + var groupID = Tw.GetStringParam(ParentBar.Pointer, ID, "group"); + if ((groupID != null) && (groupID != "")) { + return new Group(ParentBar, groupID); + } else { + return null; + } + } + + set + { + if ((value != null) && (value.ParentBar != ParentBar)) { + throw new ArgumentException("Cannot move groups across bars."); + } + + if (value != null) { + Tw.SetParam(ParentBar.Pointer, ID, "group", value.ID); + } else { + Tw.SetParam(ParentBar.Pointer, ID, "group", ""); + } + } + } + + /// + /// Gets or sets whether this group is open or closed. + /// + public Boolean Open + { + get { return Tw.GetBooleanParam(ParentBar.Pointer, ID, "opened"); } + set { Tw.SetParam(ParentBar.Pointer, ID, "opened", value); } + } + + /// + /// Gets or sets whether this group is visible or not. + /// + public Boolean Visible + { + get { return Tw.GetBooleanParam(ParentBar.Pointer, ID, "visible"); } + set { Tw.SetParam(ParentBar.Pointer, ID, "visible", value); } + } + + public override bool Equals(object obj) + { + if (obj == null) { + return false; + } + + if (!(obj is Group)) { + return false; + } + + return Equals(obj as Group); + } + + public bool Equals(Group other) + { + return (ID == other.ID); + } + + public override int GetHashCode() + { + return ID.GetHashCode(); + } + + public override String ToString() + { + return String.Format("[Group: Label={0}]", Label); + } + } +} diff --git a/Library/Library.csproj b/Library/Library.csproj index 3c62f49..0b47fba 100644 --- a/Library/Library.csproj +++ b/Library/Library.csproj @@ -47,6 +47,7 @@ + diff --git a/Library/Variable.cs b/Library/Variable.cs index ed2389b..bd7f8f1 100644 --- a/Library/Variable.cs +++ b/Library/Variable.cs @@ -87,10 +87,30 @@ public String Help /// /// Gets or sets this variable's group. /// - public String Group + public Group Group { - get { return Tw.GetStringParam(ParentBar.Pointer, ID, "group"); } - set { Tw.SetParam(ParentBar.Pointer, ID, "group", value); } + get + { + var groupID = Tw.GetStringParam(ParentBar.Pointer, ID, "group"); + if ((groupID != null) && (groupID != "")) { + return new Group(ParentBar, groupID); + } else { + return null; + } + } + + set + { + if ((value != null) && (value.ParentBar != ParentBar)) { + throw new ArgumentException("Cannot move groups across bars."); + } + + if (value != null) { + Tw.SetParam(ParentBar.Pointer, ID, "group", value.ID); + } else { + Tw.SetParam(ParentBar.Pointer, ID, "group", ""); + } + } } /// diff --git a/Sample/Program.cs b/Sample/Program.cs index 616f84c..2ee389a 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -61,7 +61,7 @@ public Polynomial Polynomial /// class ComplexVariable : IDisposable { - private readonly DoubleVariable re, im; + public readonly DoubleVariable re, im; /// /// Occurs when the user changes the variable. @@ -104,7 +104,7 @@ public ComplexVariable(Bar bar, Complex initialValue) re.Label = "Real"; im.Label = "Imaginary"; - Label = "undef"; + Label = new Group(bar, "undef", re); } public Double Step @@ -127,7 +127,7 @@ public Double Precision } } - public String Label + public Group Label { get { return re.Group; } set @@ -304,7 +304,13 @@ protected override void OnLoad(EventArgs _) } }; - Mouse.WheelChanged += (sender, e) => fractal.ZoomIn(e.DeltaPrecise); + Mouse.WheelChanged += (sender, e) => + { + if (!context.HandleMouseWheel(e.Value)) { + fractal.ZoomIn(e.DeltaPrecise); + } + }; + Mouse.Move += (sender, e) => context.HandleMouseMove(e.Position); Mouse.ButtonUp += (sender, e) => HandleMouseClick(context, e); Mouse.ButtonDown += (sender, e) => @@ -364,13 +370,13 @@ protected override void OnLoad(EventArgs _) var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; - aCoeffVar.Label = "Relaxation Coeff."; + aCoeffVar.Label = new Group(configsBar, "Relaxation Coeff.", aCoeffVar.re); aCoeffVar.Step = 0.0002; aCoeffVar.Precision = 4; var kcoeff = new ComplexVariable(configsBar, fractal.KCoeff); kcoeff.Changed += delegate { fractal.KCoeff = kcoeff.Value; }; - kcoeff.Label = "Nova Coeff."; + kcoeff.Label = new Group(configsBar, "Nova Coeff.", kcoeff.re); kcoeff.Step = 0.0002; kcoeff.Precision = 4; @@ -413,10 +419,11 @@ protected override void OnLoad(EventArgs _) * keeping a reference to them. That way we don't need to track them at all. */ + var symbolicVariables = new List(); + for (char symbol = 'A'; symbol <= 'F'; ++symbol) { var symbolVar = new DoubleVariable(fractalBar); - symbolVar.Group = "Symbolic Variables"; symbolVar.Label = symbol.ToString(); symbolVar.Step = 0.0002f; symbolVar.Precision = 4; @@ -429,10 +436,15 @@ protected override void OnLoad(EventArgs _) // also add the symbol initially, so that it exists on startup poly[symbolVar.Label] = symbolVar.Value; + symbolicVariables.Add(symbolVar); } + var grp = symbolicVariables[0].Group; + + var symbolicVarGroup = new Group(fractalBar, "Symbolic Variables", symbolicVariables); + /* Start the symbol list closed to avoid clutter */ - fractalBar.OpenGroup("Symbolic Variables", false); + symbolicVarGroup.Open = false; } private enum FractalPreset diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 8b541d7..4800abc 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -205,8 +205,8 @@ public void Help() [Test()] public void Group() { - Variable.Group = "testing"; - Assert.AreEqual("testing", Variable.Group); + var group = new Group(Bar, "Test Group", Variable); + Assert.AreEqual(group, Variable.Group); } [Test()] From 7b1ee16330bb76b1d6a7bb1607933362d70919ae Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 3 Dec 2014 22:01:48 +1300 Subject: [PATCH 17/38] Updated Sample for new groups. --- Library/Bar.cs | 11 ----------- Library/Group.cs | 24 +++++++++++++++++------- Sample/Program.cs | 12 +++--------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Library/Bar.cs b/Library/Bar.cs index 693e476..34bbcd3 100644 --- a/Library/Bar.cs +++ b/Library/Bar.cs @@ -71,17 +71,6 @@ public void SetDefinition(String def) #region Customization - /// - /// Moves a variable group into another group. - /// - /// The name of the group to move. - /// The name of the group to move it into. - public void MoveGroup(String group, String into) - { - Tw.SetCurrentWindow(ParentContext.Identifier); - Tw.Define(String.Format("{0}/`{1}` group=`{2}`", ID, group, into)); - } - /// /// Gets or sets this bar's label. /// diff --git a/Library/Group.cs b/Library/Group.cs index 3d01e77..e3b43b9 100644 --- a/Library/Group.cs +++ b/Library/Group.cs @@ -35,25 +35,35 @@ internal Group(Bar bar, String id) this.id = id; } + /// + /// Lazily creates a new group in a given bar. The group is not + /// usable until it is assigned to contain at least one variable. + /// + /// The bar the new group should belong to. + public Group(Bar parentBar) + { + if (parentBar == null) { + throw new ArgumentNullException("parentBar"); + } + + id = Guid.NewGuid().ToString(); + this.parentBar = parentBar; + } + /// /// Creates a new group in a given bar and puts variables in it. /// /// The bar the new group should belong to. /// A label to display for the new group. /// Variables to put in the new group. - public Group(Bar parentBar, String label, params Variable[] variables) + public Group(Bar parentBar, String label, params Variable[] variables) : this(parentBar) { - if (parentBar == null) { - throw new ArgumentNullException("parentBar"); - } else if (label == null) { + if (label == null) { throw new ArgumentNullException("label"); } else if (variables == null) { throw new ArgumentNullException("variables"); } - id = Guid.NewGuid().ToString(); - this.parentBar = parentBar; - foreach (var variable in variables) { variable.Group = this; } diff --git a/Sample/Program.cs b/Sample/Program.cs index 2ee389a..97448fd 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -85,11 +85,8 @@ public Complex Value get { return new Complex(re.Value, im.Value); } set { - bool changed = !value.Equals(Value); re.Value = value.Real; im.Value = value.Imaginary; - if (changed) - OnChanged(EventArgs.Empty); } } @@ -419,12 +416,13 @@ protected override void OnLoad(EventArgs _) * keeping a reference to them. That way we don't need to track them at all. */ - var symbolicVariables = new List(); + var symbolicVarGroup = new Group(fractalBar); for (char symbol = 'A'; symbol <= 'F'; ++symbol) { var symbolVar = new DoubleVariable(fractalBar); symbolVar.Label = symbol.ToString(); + symbolVar.Group = symbolicVarGroup; symbolVar.Step = 0.0002f; symbolVar.Precision = 4; @@ -436,14 +434,10 @@ protected override void OnLoad(EventArgs _) // also add the symbol initially, so that it exists on startup poly[symbolVar.Label] = symbolVar.Value; - symbolicVariables.Add(symbolVar); } - var grp = symbolicVariables[0].Group; - - var symbolicVarGroup = new Group(fractalBar, "Symbolic Variables", symbolicVariables); - /* Start the symbol list closed to avoid clutter */ + symbolicVarGroup.Label = "Symbolic Variables"; symbolicVarGroup.Open = false; } From 801e5505ed0482652c62ea2fae7338b4421501e9 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 3 Dec 2014 22:17:58 +1300 Subject: [PATCH 18/38] Updated readme. --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d6dd98a..e1469a3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Quick Start You must obtain and install the native AntTweakBar library itself from its [SourceForge page](http://anttweakbar.sourceforge.net/doc/tools:anttweakbar:download), if you haven't already. For Windows, download the appropriate prebuilt DLL's and install them on your system or as third party libraries in your C# project. For Linux, simply `make && make install` as usual. Then add the `AntTweakBar.NET.dll` assembly in your project, either by compiling it from the repository or retrieving it from [NuGet](https://www.nuget.org/packages/AntTweakBar.NET/). You're good to go! -The AntTweakBar.NET high-level interface is divided into three main concepts: contexts, bars, and variables. +The AntTweakBar.NET high-level interface is divided into four main concepts: contexts, bars, variables and groups. - **Context**: An instance of this class conceptually maps to a graphical window in your application: each window that will contain bars should have its own context. Each context holds its own separate set of bars, and has several methods to send window events to AntTweakBar, and, of course, draw the bars into the window. @@ -23,6 +23,8 @@ The AntTweakBar.NET high-level interface is divided into three main concepts: co - **Variable**: This is the base class from which all other variable types (like `IntVariable` or `StringVariable`) descend from. Just like the `Bar` class, it and its descendants have plenty of properties you can modify to tweak the variable's behavior and graphical appearance. In addition, value variables hold a value property which can be set graphically by the user and can be read on the fly by your code, this is the `Value` property for simple types (like `IntVariable`) or e.g. the `X`, `Y`, `Z` properties for the `VectorVariable`. They also have a `Changed` event to be notified when the user changes the variable's value. The `Button` variable type has a `Clicked` event instead. Each variable belongs to a bar passed to its constructor. +- **Group**: These are used to put a set of variables together in the bar for easy access, you can open (expand) or close (collapse) groups, and can put groups into groups for a hierarchical organization. Please see "Using groups" in the advanced usage section below to find out how to use them. + The first context created should be passed the graphics API you are using, which is some version of OpenGL or DirectX. For DirectX, you must also pass a pointer to the native device, which should be available from the graphics framework you are using somehow (for instance, for SharpDX, use `SharpDX.Direct3D11.Device.NativePointer`). ```csharp @@ -86,6 +88,22 @@ Advanced Usage This definition string follows the same format as documented on the AntTweakBar website under `TwDefine`, except it should not contain the variable's name, as you don't know what it is (it is automatically filled in by the method). + - **Using groups** + + You can create a group as follows for instance: + + ```csharp + myVar.Group = new Group(myBar); + ``` + + You can nest groups into groups by using the `Parent` property of groups, and you can change their labels (so you can have two groups with the same label if you want). Note `null` is a valid group which represents the root of the bar (i.e. no group). There are three gotchas to watch out for when using groups: + + \* A group only exists as long as there are variables under said group. This means that until you assign a variable to a group, you cannot modify the group's label or any of its properties. As a result, the `Group` class supports two kinds of initializations: lazy initialization, where you can create a non-existent group, assign it to a bunch of variables (which truly creates it) and configure it afterwards, or direct initialization, where the constructor takes a collection of initial variables to put under the group. Use whichever one is more convenient for you. + + \* A group ceases to exist as soon as there are no more variables under said group. This is not a big problem, it's just important to keep that in mind when dynamically creating variables as it may invalidate `Group` instances you are holding. + + \* Take care when putting groups into groups: if you put group A into group B, and then put group B into group A, both groups and all variables contained in them will be destroyed (this is done automatically by the native library). In general, avoid circular group references and use the `null` group to break such circular references. + - **Defining custom variables** All the classes in the wrapper are sealed. In most cases you should favor composing variables together to extend their functionality, as done for instance in the sample, where a complex variable type is created by composing two `DoubleVariable` instances, and a polynomial variable type is implemented by adding custom validation logic to a `StringVariable` to make it only accept polynomial formulas. In general, the `Validating` event can be used to introduce additional requirements on the contents of the variable. The variable's value will be changed if and only if it passes validation by *all* event handlers attached to the variable's `Validating` event. @@ -146,6 +164,7 @@ Next release: - changed Sample to use GLSL version 120, fixed shader saving overwrite bug and close on Esc. - added TwDefineEnum and TwDefineStruct native functions and a DefineEnum low-level wrapper - various miscellaneous fixes and improvements to the Sample + - added `Group` class and improved code relating to variable groups 28 November 2014 (v0.4.4) From 4a4cd210f21a2c815afb96593a845ec86588069d Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 3 Dec 2014 22:21:56 +1300 Subject: [PATCH 19/38] Fixed readme. --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1469a3..fd4713d 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,11 @@ Advanced Usage You can create a group as follows for instance: ```csharp - myVar.Group = new Group(myBar); + var myGroup = new Group(myBar, "A group", myVar); + /* or */ + var myGroup = new Group(myBar); + myVar.Group = myGroup; + myGroup.Label = "A group"; ``` You can nest groups into groups by using the `Parent` property of groups, and you can change their labels (so you can have two groups with the same label if you want). Note `null` is a valid group which represents the root of the bar (i.e. no group). There are three gotchas to watch out for when using groups: From 9f89741e53682f5b18a99fd5c5deeec89948995c Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 3 Dec 2014 23:52:13 +1300 Subject: [PATCH 20/38] Testing some StructVariable ideas. --- Library/Bar.cs | 10 +-- Library/BoolVariable.cs | 2 +- Library/Color4Variable.cs | 2 +- Library/ColorVariable.cs | 2 +- Library/DoubleVariable.cs | 2 +- Library/EnumVariable.cs | 2 +- Library/FloatVariable.cs | 2 +- Library/IntVariable.cs | 2 +- Library/QuaternionVariable.cs | 2 +- Library/StringVariable.cs | 2 +- Library/StructVariable.cs | 154 +++++++++++++--------------------- Library/Variable.cs | 31 ++++++- Library/VectorVariable.cs | 2 +- Sample/Program.cs | 111 ++++++------------------ 14 files changed, 127 insertions(+), 199 deletions(-) diff --git a/Library/Bar.cs b/Library/Bar.cs index 34bbcd3..703a14b 100644 --- a/Library/Bar.cs +++ b/Library/Bar.cs @@ -10,7 +10,7 @@ namespace AntTweakBar /// /// An AntTweakBar bar, which holds a set of variables. /// - public sealed class Bar : IEnumerable, IDisposable + public sealed class Bar : IEnumerable, IDisposable { /// /// The default label for unnamed bars. @@ -190,14 +190,14 @@ public void SendToBack() #region IEnumerable - private readonly ICollection variables = new HashSet(); + private readonly ICollection variables = new HashSet(); - internal void Add(Variable variable) + internal void Add(IVariable variable) { variables.Add(variable); } - internal void Remove(Variable variable) + internal void Remove(IVariable variable) { variables.Remove(variable); } @@ -214,7 +214,7 @@ public void Clear() } } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return variables.GetEnumerator(); } diff --git a/Library/BoolVariable.cs b/Library/BoolVariable.cs index a654c70..a1f46cf 100644 --- a/Library/BoolVariable.cs +++ b/Library/BoolVariable.cs @@ -22,7 +22,7 @@ public BoolValidationEventArgs(bool value) /// /// An AntTweakBar variable which can hold a boolean value. /// - public sealed class BoolVariable : Variable + public sealed class BoolVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/Color4Variable.cs b/Library/Color4Variable.cs index 9b5767d..65dadf9 100644 --- a/Library/Color4Variable.cs +++ b/Library/Color4Variable.cs @@ -28,7 +28,7 @@ public Color4ValidationEventArgs(float r, float g, float b, float a) /// /// An AntTweakBar variable which can hold an RGBA color value. /// - public sealed class Color4Variable : Variable + public sealed class Color4Variable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/ColorVariable.cs b/Library/ColorVariable.cs index bcbbefc..762e10a 100644 --- a/Library/ColorVariable.cs +++ b/Library/ColorVariable.cs @@ -26,7 +26,7 @@ public ColorValidationEventArgs(float r, float g, float b) /// /// An AntTweakBar variable which can hold an RGB color value. /// - public sealed class ColorVariable : Variable + public sealed class ColorVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/DoubleVariable.cs b/Library/DoubleVariable.cs index 87d6c62..aeae564 100644 --- a/Library/DoubleVariable.cs +++ b/Library/DoubleVariable.cs @@ -22,7 +22,7 @@ public DoubleValidationEventArgs(double value) /// /// An AntTweakBar variable which can hold a double-precision floating-point number. /// - public sealed class DoubleVariable : Variable + public sealed class DoubleVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/EnumVariable.cs b/Library/EnumVariable.cs index 8d0caa3..888d456 100644 --- a/Library/EnumVariable.cs +++ b/Library/EnumVariable.cs @@ -24,7 +24,7 @@ public EnumValidationEventArgs(T value) /// /// An AntTweakBar variable which can hold an enum. /// - public sealed class EnumVariable : Variable where T : struct + public sealed class EnumVariable : Variable, IValueVariable where T : struct { /// /// Occurs when the user changes this variable's value. diff --git a/Library/FloatVariable.cs b/Library/FloatVariable.cs index dff2f6a..fdd373c 100644 --- a/Library/FloatVariable.cs +++ b/Library/FloatVariable.cs @@ -22,7 +22,7 @@ public FloatValidationEventArgs(float value) /// /// An AntTweakBar variable which can hold a single-precision floating-point number. /// - public sealed class FloatVariable : Variable + public sealed class FloatVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/IntVariable.cs b/Library/IntVariable.cs index 5455eeb..3656def 100644 --- a/Library/IntVariable.cs +++ b/Library/IntVariable.cs @@ -22,7 +22,7 @@ public IntValidationEventArgs(int value) /// /// An AntTweakBar variable which can hold an integer. /// - public sealed class IntVariable : Variable + public sealed class IntVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/QuaternionVariable.cs b/Library/QuaternionVariable.cs index 5fec18a..2605e46 100644 --- a/Library/QuaternionVariable.cs +++ b/Library/QuaternionVariable.cs @@ -29,7 +29,7 @@ public QuaternionValidationEventArgs(float x, float y, float z, float w) /// /// An AntTweakBar variable which can hold a quaternion. /// - public sealed class QuaternionVariable : Variable + public sealed class QuaternionVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/StringVariable.cs b/Library/StringVariable.cs index 4ccd51b..259b817 100644 --- a/Library/StringVariable.cs +++ b/Library/StringVariable.cs @@ -24,7 +24,7 @@ public StringValidationEventArgs(String value) /// /// An AntTweakBar variable which can hold a string. /// - public sealed class StringVariable : Variable + public sealed class StringVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Library/StructVariable.cs b/Library/StructVariable.cs index 08c894b..38f3cd5 100644 --- a/Library/StructVariable.cs +++ b/Library/StructVariable.cs @@ -6,35 +6,13 @@ namespace AntTweakBar { - public sealed class StructValidationEventArgs : EventArgs where T : struct - { - /// - /// Whether to accept this integer value. - /// - public bool Valid { get; set; } - public T Value { get; private set; } - - public StructValidationEventArgs(T value) - { - Value = value; - } - } - - /// - /// An AntTweakBar variable which can hold a composite struct. - /// - public sealed class StructVariable : Variable where T : struct + public abstract class StructVariable : IValueVariable { /// /// Occurs when the user changes this variable's value. /// public event EventHandler Changed; - /// - /// Occurs when the new value of this variable is validated. - /// - public event EventHandler> Validating; - /// /// Raises the Changed event. /// @@ -48,116 +26,96 @@ public void OnChanged(EventArgs e) } /// - /// Gets or sets the value of this variable. + /// Gets this variable's parent bar. /// - public T Value - { - get { ThrowIfDisposed(); return value; } - set { ValidateAndSet(value); } - } - + public Bar ParentBar { get { return parentBar; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private T value; + private readonly Bar parentBar; /// - /// Gets this struct variable's type. + /// Gets or sets the value of this variable. /// - public Tw.VariableType Type { get { ThrowIfDisposed(); return type; } } + public abstract T Value { get; set; } [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private Tw.VariableType type; + protected IList variables; - /// - /// Initialization delegate, which creates the struct variable. - /// - private static void InitStructVariable(Variable var, String id, IDictionary members) + public StructVariable(Bar bar, params IValueVariable[] variables) { - if (!(typeof(T).IsValueType && !typeof(T).IsPrimitive && !typeof(T).IsEnum)) { - throw new ArgumentException(String.Format("Type {0} is not a struct.", typeof(T).FullName)); + if (bar == null) { + throw new ArgumentNullException("bar"); + } else if (variables == null) { + throw new ArgumentNullException("variables"); + } else if (variables.Length == 0) { + throw new ArgumentException("At least one variable."); + } else if (variables.Any((var) => (var.ParentBar != bar))) { + throw new ArgumentException("All variables must be in the same bar."); } - var it = var as StructVariable; - - Variable.SetCallbacks.Add(id, new Tw.SetVarCallback(it.SetCallback)); - Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); + this.variables = new List(variables); + this.parentBar = bar; - Tw.AddVarCB(var.ParentBar.Pointer, id, - it.type = Tw.DefineStruct(Guid.NewGuid().ToString(), members, (int)Marshal.SizeOf(typeof(T)), null, IntPtr.Zero), - Variable.SetCallbacks[id], - Variable.GetCallbacks[id], - IntPtr.Zero, null); + foreach (var variable in this.variables) { + variable.Changed += (s, e) => { + OnChanged(e); + }; + } } - /// - /// Creates a new struct variable in a given bar. - /// - /// The bar to create the struct variable in. - /// The set of struct members to consider. - /// The initial value of the variable. - /// An optional definition string for the new variable. - public StructVariable(Bar bar, IDictionary members, T initialValue = default(T), String def = null) - : base(bar, (var, id) => InitStructVariable(var, id, members), def) + public Group Group { - Validating += (s, e) => { e.Valid = true; }; - - ValidateAndSet(initialValue); + get { return (variables.First() as Variable).Group; } + set + { + foreach (var variable in variables) { + (variable as Variable).Group = value; + } + } } - /// - /// Checks if this variable can hold this value. - /// - private bool IsValid(T value) - { - ThrowIfDisposed(); + #region IDisposable - return !Validating.GetInvocationList().Select(h => { - var check = new StructValidationEventArgs(value); - h.DynamicInvoke(new object[] { this, check }); - return !check.Valid; - }).Any(failed => failed); + ~StructVariable() + { + Dispose(false); } - /// - /// Tries to set this variable's value, validating it. - /// - private void ValidateAndSet(T value) + public void Dispose() { - if (!IsValid(value)) { - throw new ArgumentException("Invalid variable value."); - } else { - this.value = value; - } + Dispose(true); + GC.SuppressFinalize(this); } - /// - /// Called by AntTweakBar when the user changes the variable's value. - /// - private void SetCallback(IntPtr pointer, IntPtr clientData) + private void Dispose(bool disposing) { - T data = (T)Marshal.PtrToStructure(pointer, typeof(T)); - - if (IsValid(data)) + if (!disposed) { - bool changed = !value.Equals(data); - value = data; - - if (changed) { - OnChanged(EventArgs.Empty); + if (disposing) { + foreach (var variable in variables) { + variable.Dispose(); + } } + + disposed = true; } } + private bool disposed = false; + /// - /// Called by AntTweakBar when AntTweakBar needs the variable's value. + /// Throws an ObjectDisposedException if this variable has been disposed. /// - private void GetCallback(IntPtr pointer, IntPtr clientData) + protected void ThrowIfDisposed() { - Marshal.StructureToPtr(Value, pointer, false); + if (disposed) { + throw new ObjectDisposedException(GetType().FullName); + } } + #endregion + public override String ToString() { - return String.Format("[StructVariable: Label={0}, Value={1}]", Label, Value); + return String.Format("[StructVariable<{0}>: Label={1}, Value={2}]", typeof(T).Name, null /* TODO */, Value); } } } - diff --git a/Library/Variable.cs b/Library/Variable.cs index bd7f8f1..5cbef73 100644 --- a/Library/Variable.cs +++ b/Library/Variable.cs @@ -6,10 +6,37 @@ namespace AntTweakBar { + /// + /// The interface implemented by all AntTweakBar variables. + /// + public interface IVariable : IDisposable + { + /// + /// Gets this variable's parent bar. + /// + Bar ParentBar { get; } + } + + /// + /// The interface implemented by all AntTweakBar value variables. + /// + public interface IValueVariable : IVariable + { + /// + /// Occurs when the user changes this variable's value. + /// + event EventHandler Changed; + + /// + /// Raises the Changed event. + /// + void OnChanged(EventArgs e); + } + /// /// The base class for all AntTweakBar variables. /// - public abstract class Variable : IDisposable + public abstract class Variable : IVariable { /// /// The default label for unnamed variables. @@ -173,7 +200,7 @@ public void Dispose() GC.SuppressFinalize(this); } - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { if (!disposed && (ParentBar != null)) { diff --git a/Library/VectorVariable.cs b/Library/VectorVariable.cs index 0528ec4..a4df784 100644 --- a/Library/VectorVariable.cs +++ b/Library/VectorVariable.cs @@ -27,7 +27,7 @@ public VectorValidationEventArgs(float x, float y, float z) /// /// An AntTweakBar variable which can hold a 3D vector. /// - public sealed class VectorVariable : Variable + public sealed class VectorVariable : Variable, IValueVariable { /// /// Occurs when the user changes this variable's value. diff --git a/Sample/Program.cs b/Sample/Program.cs index 97448fd..c0dc62d 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -55,112 +55,49 @@ public Polynomial Polynomial } } - /// - /// Not a real variable (just contains two variables for the - /// real/imaginary parts and puts them in a single group). - /// - class ComplexVariable : IDisposable + class ComplexVariable : StructVariable { - public readonly DoubleVariable re, im; - - /// - /// Occurs when the user changes the variable. - /// - public event EventHandler Changed; - - /// - /// Raises the Changed event. - /// - public void OnChanged(EventArgs e) + public ComplexVariable(Bar bar, Complex initialValue) : base(bar, new DoubleVariable(bar, 0, "label=Real"), new DoubleVariable(bar, 0, "label=Imaginary")) { - if (Changed != null) - Changed(this, e); + Value = initialValue; } - /// - /// Gets or sets the value of this variable. - /// - public Complex Value + public override Complex Value { - get { return new Complex(re.Value, im.Value); } - set + get { - re.Value = value.Real; - im.Value = value.Imaginary; + return new Complex( + (variables[0] as DoubleVariable).Value, + (variables[1] as DoubleVariable).Value + ); } - } - - public ComplexVariable(Bar bar, Complex initialValue) - { - re = new DoubleVariable(bar, initialValue.Real); - im = new DoubleVariable(bar, initialValue.Imaginary); - - re.Changed += (sender, e) => OnChanged(EventArgs.Empty); - im.Changed += (sender, e) => OnChanged(EventArgs.Empty); - re.Label = "Real"; - im.Label = "Imaginary"; - - Label = new Group(bar, "undef", re); - } - - public Double Step - { - get { return re.Step; } set { - re.Step = value; - im.Step = value; + (variables[0] as DoubleVariable).Value = value.Real; + (variables[1] as DoubleVariable).Value = value.Imaginary; } } - public Double Precision + public Double Step { - get { return re.Precision; } + get { return (variables[0] as DoubleVariable).Step; } set { - re.Precision = value; - im.Precision = value; + (variables[0] as DoubleVariable).Step = value; + (variables[1] as DoubleVariable).Step = value; } } - public Group Label + public Double Precision { - get { return re.Group; } + get { return (variables[0] as DoubleVariable).Precision; } set { - re.Group = value; - im.Group = value; + (variables[0] as DoubleVariable).Precision = value; + (variables[1] as DoubleVariable).Precision = value; } } - - #region IDisposable - - ~ComplexVariable() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposed) - return; - - re.Dispose(); - im.Dispose(); - - disposed = true; - } - - private bool disposed = false; - - #endregion } class Program : GameWindow @@ -365,18 +302,24 @@ protected override void OnLoad(EventArgs _) intensityVar.SetDefinition("min=0 max=3 step=0.01 precision=2)"); intensityVar.Label = "Intensity"; + var aCoeffGroup = new Group(configsBar); var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; - aCoeffVar.Label = new Group(configsBar, "Relaxation Coeff.", aCoeffVar.re); + aCoeffVar.Group = aCoeffGroup; aCoeffVar.Step = 0.0002; aCoeffVar.Precision = 4; + aCoeffGroup.Label = "Relaxation Coefficient"; + + var kCoeffGroup = new Group(configsBar); var kcoeff = new ComplexVariable(configsBar, fractal.KCoeff); kcoeff.Changed += delegate { fractal.KCoeff = kcoeff.Value; }; - kcoeff.Label = new Group(configsBar, "Nova Coeff.", kcoeff.re); + kcoeff.Group = kCoeffGroup; kcoeff.Step = 0.0002; kcoeff.Precision = 4; + kCoeffGroup.Label = "Nova Coefficient"; + /* Set up a bar for the user to play with the equation */ var fractalBar = new Bar(context, "label='Fractal Equation' valueswidth=400"); From c473992716d5a452443f80ffd65ce42130a65e2f Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Sat, 6 Dec 2014 16:22:50 +1300 Subject: [PATCH 21/38] Some input handling experiments. --- Library/Context.cs | 64 +++++++++++--- Library/Library.csproj | 1 + Library/Mappings.cs | 110 ++++++++++++++++++++++++ Library/Native.cs | 8 +- Library/Types.cs | 30 ++++++- Sample/Program.cs | 190 +++++++++++++++++++++++------------------ 6 files changed, 298 insertions(+), 105 deletions(-) create mode 100644 Library/Mappings.cs diff --git a/Library/Context.cs b/Library/Context.cs index 0694062..d1ae5f1 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -139,35 +139,69 @@ public bool HandleMouseClick(Tw.MouseAction action, Tw.MouseButton button) return Tw.MouseClick(action, button); } + private bool ignoreKeyPress = false; + /// - /// Notifies this context of a key press. + /// Notifies this context that a character has been typed. /// - /// The key character pressed. - public bool HandleKeyPress(char key) + /// The character typed. + public bool HandleKeyPress(char character) { Tw.SetCurrentWindow(Identifier); - return Tw.KeyPressed((int)key, Tw.KeyModifier.None); + + if (!ignoreKeyPress) { + return Tw.KeyPressed((int)character, Tw.KeyModifiers.None); + } else { + return false; + } } /// - /// Tests this context for whether a key press would be handled. + /// Notifies this context that a key has been pressed. /// - /// The key character pressed. - public bool HandleKeyTest(char key) + /// The key pressed. + /// The key modifiers. + public bool HandleKeyDown(Tw.Key key, Tw.KeyModifiers modifiers) { + // TODO + Tw.SetCurrentWindow(Identifier); - return Tw.KeyTest((int)key, Tw.KeyModifier.None); + + var handled = Tw.KeyPressed((int)key, modifiers); + + if (handled) { + if (((int)key >= (int)'A') && ((int)key <= (int)'Z')) { + ignoreKeyPress = true; + } + + if (key == Tw.Key.Space) { + ignoreKeyPress = true; + } + } + + return handled; } /// - /// Notifies this context of a special key press. + /// Notifies this context that a key has been released. /// - /// The key pressed. - /// The key modifiers pressed. - public bool HandleKeyPress(Tw.SpecialKey key, Tw.KeyModifier modifiers) + /// The key released. + /// The key modifiers. + public bool HandleKeyUp(Tw.Key key, Tw.KeyModifiers modifiers) + { + return (ignoreKeyPress = false); + } + +#if false + + /// + /// Tests this context for whether a key press would be handled. + /// + /// The key character pressed. + public bool HandleKeyTest(char key) { Tw.SetCurrentWindow(Identifier); - return Tw.KeyPressed((int)key, modifiers); + return Tw.KeyTest((int)key, Tw.KeyModifiers.None); } /// @@ -175,12 +209,14 @@ public bool HandleKeyPress(Tw.SpecialKey key, Tw.KeyModifier modifiers) /// /// The key pressed. /// The key modifiers pressed. - public bool HandleKeyTest(Tw.SpecialKey key, Tw.KeyModifier modifiers) + public bool HandleKeyTest(Tw.SpecialKey key, Tw.KeyModifiers modifiers) { Tw.SetCurrentWindow(Identifier); return Tw.KeyTest((int)key, modifiers); } +#endif + /// /// The SFML event handler. /// diff --git a/Library/Library.csproj b/Library/Library.csproj index 0b47fba..6e20d9b 100644 --- a/Library/Library.csproj +++ b/Library/Library.csproj @@ -69,6 +69,7 @@ + \ No newline at end of file diff --git a/Library/Mappings.cs b/Library/Mappings.cs new file mode 100644 index 0000000..93c1624 --- /dev/null +++ b/Library/Mappings.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; + +namespace AntTweakBar +{ + /// + /// A low-level wrapper to the AntTweakBar API. + /// + public static partial class Tw + { + /// + /// AntTweakBar input mappings for some graphics frameworks. + /// + public static class Mappings + { + /// + /// OpenTK to AntTweakBar input mappings. + /// + public static class OpenTK + { + /// + /// Input mapping for mouse buttons. + /// + public static IDictionary Buttons = new Dictionary() + { + { 0, Tw.MouseButton.Left }, + { 1, Tw.MouseButton.Middle }, + { 2, Tw.MouseButton.Right }, + }; + + /// + /// Input mapping for key modifiers. + /// + public static IDictionary Modifiers = new Dictionary() + { + { 1 << 0, Tw.KeyModifiers.Alt }, + { 1 << 1, Tw.KeyModifiers.Ctrl }, + { 1 << 2, Tw.KeyModifiers.Shift }, + }; + + /// + /// Input mapping for keyboard keys. + /// + public static IDictionary Keys = new Dictionary() + { + { 0, Tw.Key.None }, + { 53, Tw.Key.Backspace }, + { 52, Tw.Key.Tab }, + { 65, Tw.Key.Clear }, + { 49, Tw.Key.Return }, + { 63, Tw.Key.Pause }, + { 50, Tw.Key.Escape }, + { 51, Tw.Key.Space }, + { 55, Tw.Key.Delete }, + { 45, Tw.Key.Up }, + { 46, Tw.Key.Down }, + { 48, Tw.Key.Right }, + { 47, Tw.Key.Left }, + { 54, Tw.Key.Insert }, + { 58, Tw.Key.Home }, + { 59, Tw.Key.End }, + { 56, Tw.Key.PageUp }, + { 57, Tw.Key.PageDown }, + { 10, Tw.Key.F1 }, + { 11, Tw.Key.F2 }, + { 12, Tw.Key.F3 }, + { 13, Tw.Key.F4 }, + { 14, Tw.Key.F5 }, + { 15, Tw.Key.F6 }, + { 16, Tw.Key.F7 }, + { 17, Tw.Key.F8 }, + { 18, Tw.Key.F9 }, + { 19, Tw.Key.F10 }, + { 20, Tw.Key.F11 }, + { 21, Tw.Key.F12 }, + { 22, Tw.Key.F13 }, + { 23, Tw.Key.F14 }, + { 24, Tw.Key.F15 }, + { 83, Tw.Key.A }, + { 84, Tw.Key.B }, + { 85, Tw.Key.C }, + { 86, Tw.Key.D }, + { 87, Tw.Key.E }, + { 88, Tw.Key.F }, + { 89, Tw.Key.G }, + { 90, Tw.Key.H }, + { 91, Tw.Key.I }, + { 92, Tw.Key.J }, + { 93, Tw.Key.K }, + { 94, Tw.Key.L }, + { 95, Tw.Key.M }, + { 96, Tw.Key.N }, + { 97, Tw.Key.O }, + { 98, Tw.Key.P }, + { 99, Tw.Key.Q }, + { 100, Tw.Key.R }, + { 101, Tw.Key.S }, + { 102, Tw.Key.T }, + { 103, Tw.Key.U }, + { 104, Tw.Key.V }, + { 105, Tw.Key.W }, + { 106, Tw.Key.X }, + { 107, Tw.Key.Y }, + { 108, Tw.Key.Z }, + }; + } + } + } +} + diff --git a/Library/Native.cs b/Library/Native.cs index f7169e9..0538983 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -62,13 +62,13 @@ internal static class NativeMethods [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean TwKeyPressed( [In] Int32 key, - [In] Tw.KeyModifier modifiers); + [In] Tw.KeyModifiers modifiers); [DllImport(DLLName, EntryPoint = "TwKeyTest")] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean TwKeyTest( [In] Int32 key, - [In] Tw.KeyModifier modifiers); + [In] Tw.KeyModifiers modifiers); [DllImport(DLLName, EntryPoint = "TwEventSFML")] [return: MarshalAs(UnmanagedType.Bool)] @@ -453,7 +453,7 @@ public static bool MouseClick(MouseAction action, MouseButton button) /// The ASCII code of the pressed key, or one of the codes. /// One or a combination of the constants. /// Whether the key event has been handled by AntTweakBar. - public static bool KeyPressed(int key, KeyModifier modifiers) + public static bool KeyPressed(int key, KeyModifiers modifiers) { return NativeMethods.TwKeyPressed(key, modifiers); } @@ -464,7 +464,7 @@ public static bool KeyPressed(int key, KeyModifier modifiers) /// The ASCII code of the pressed key, or one of the codes. /// One or a combination of the constants. /// Whether the key event would have been handled by AntTweakBar. - public static bool KeyTest(int key, KeyModifier modifiers) + public static bool KeyTest(int key, KeyModifiers modifiers) { return NativeMethods.TwKeyTest(key, modifiers); } diff --git a/Library/Types.cs b/Library/Types.cs index edfa6d0..f9bb953 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -340,7 +340,7 @@ public enum MouseButton /// Specifies the possible key modifiers recognized by AntTweakBar. /// [Flags] - public enum KeyModifier + public enum KeyModifiers { /// /// Represents no key modifier. @@ -367,7 +367,7 @@ public enum KeyModifier /// /// Specifies the possible special keys recognized by AntTweakBar. /// - public enum SpecialKey + public enum Key { None = 0, Backspace = '\b', @@ -402,6 +402,32 @@ public enum SpecialKey F13 = 294, F14 = 295, F15 = 296, + A = 'A', + B = 'B', + C = 'C', + D = 'D', + E = 'E', + F = 'F', + G = 'G', + H = 'H', + I = 'I', + J = 'J', + K = 'K', + L = 'L', + M = 'M', + N = 'N', + O = 'O', + P = 'P', + Q = 'Q', + R = 'R', + S = 'S', + T = 'T', + U = 'U', + V = 'V', + W = 'W', + X = 'X', + Y = 'Y', + Z = 'Z', } } } diff --git a/Sample/Program.cs b/Sample/Program.cs index c0dc62d..09a3bc3 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -145,69 +145,45 @@ private static bool HandleMouseClick(Context context, MouseButtonEventArgs e) { var action = e.IsPressed ? Tw.MouseAction.Pressed : Tw.MouseAction.Released; - switch (e.Button) - { - case MouseButton.Left: - return context.HandleMouseClick(action, Tw.MouseButton.Left); - case MouseButton.Right: - return context.HandleMouseClick(action, Tw.MouseButton.Right); - case MouseButton.Middle: - return context.HandleMouseClick(action, Tw.MouseButton.Middle); + if (Tw.Mappings.OpenTK.Buttons.ContainsKey((int)e.Button)) { + return context.HandleMouseClick(action, Tw.Mappings.OpenTK.Buttons[(int)e.Button]); + } else { + return false; } - - return false; } - private static bool HandleKeyPress(Context context, KeyboardKeyEventArgs e) + private static bool HandleKeyDown(Context context, KeyboardKeyEventArgs e) { - var modifier = Tw.KeyModifier.None; + var modifiers = Tw.KeyModifiers.None; if (e.Modifiers.HasFlag(KeyModifiers.Alt)) - modifier |= Tw.KeyModifier.Alt; + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Alt]; if (e.Modifiers.HasFlag(KeyModifiers.Shift)) - modifier |= Tw.KeyModifier.Shift; + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Shift]; if (e.Modifiers.HasFlag(KeyModifiers.Control)) - modifier |= Tw.KeyModifier.Ctrl; + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Control]; - var mapping = new Dictionary() - { - { Key.Back, Tw.SpecialKey.Backspace }, - { Key.Tab, Tw.SpecialKey.Tab }, - { Key.Clear, Tw.SpecialKey.Clear }, - { Key.Enter, Tw.SpecialKey.Return }, - { Key.Pause, Tw.SpecialKey.Pause }, - { Key.Escape, Tw.SpecialKey.Escape }, - //{ Key.Space, TW.SpecialKey.Space }, // already handled by KeyPress - { Key.Delete, Tw.SpecialKey.Delete }, - { Key.Up, Tw.SpecialKey.Up }, - { Key.Left, Tw.SpecialKey.Left }, - { Key.Down, Tw.SpecialKey.Down }, - { Key.Right, Tw.SpecialKey.Right }, - { Key.Insert, Tw.SpecialKey.Insert }, - { Key.Home, Tw.SpecialKey.Home }, - { Key.End, Tw.SpecialKey.End }, - { Key.PageUp, Tw.SpecialKey.PageUp }, - { Key.PageDown, Tw.SpecialKey.PageDown }, - { Key.F1, Tw.SpecialKey.F1 }, - { Key.F2, Tw.SpecialKey.F2 }, - { Key.F3, Tw.SpecialKey.F3 }, - { Key.F4, Tw.SpecialKey.F4 }, - { Key.F5, Tw.SpecialKey.F5 }, - { Key.F6, Tw.SpecialKey.F6 }, - { Key.F7, Tw.SpecialKey.F7 }, - { Key.F8, Tw.SpecialKey.F8 }, - { Key.F9, Tw.SpecialKey.F9 }, - { Key.F10, Tw.SpecialKey.F10 }, - { Key.F11, Tw.SpecialKey.F11 }, - { Key.F12, Tw.SpecialKey.F12 }, - { Key.F13, Tw.SpecialKey.F13 }, - { Key.F14, Tw.SpecialKey.F14 }, - { Key.F15, Tw.SpecialKey.F15 }, - }; + if (Tw.Mappings.OpenTK.Keys.ContainsKey((int)e.Key)) { + return context.HandleKeyDown(Tw.Mappings.OpenTK.Keys[(int)e.Key], modifiers); + } else { + return false; + } + } - if (mapping.ContainsKey(e.Key)) - return context.HandleKeyPress(mapping[e.Key], modifier); - else + private static bool HandleKeyUp(Context context, KeyboardKeyEventArgs e) + { + var modifiers = Tw.KeyModifiers.None; + if (e.Modifiers.HasFlag(KeyModifiers.Alt)) + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Alt]; + if (e.Modifiers.HasFlag(KeyModifiers.Shift)) + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Shift]; + if (e.Modifiers.HasFlag(KeyModifiers.Control)) + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Control]; + + if (Tw.Mappings.OpenTK.Keys.ContainsKey((int)e.Key)) { + return context.HandleKeyUp(Tw.Mappings.OpenTK.Keys[(int)e.Key], modifiers); + } else { return false; + } } #endregion @@ -227,38 +203,6 @@ protected override void OnLoad(EventArgs _) context = new Context(Tw.GraphicsAPI.OpenGL); fractal = new Fractal(); - /* Hook up the different events to the AntTweakBar.NET context, and - * allow the user to navigate the fractal using the keyboard/mouse. */ - - KeyPress += (sender, e) => context.HandleKeyPress(e.KeyChar); - Resize += (sender, e) => context.HandleResize(ClientSize); - KeyDown += (sender, e) => { - if (!HandleKeyPress(context, e) && (e.Key == Key.Escape)) { - Close(); // Close program on Esc when appropriate - } - }; - - Mouse.WheelChanged += (sender, e) => - { - if (!context.HandleMouseWheel(e.Value)) { - fractal.ZoomIn(e.DeltaPrecise); - } - }; - - Mouse.Move += (sender, e) => context.HandleMouseMove(e.Position); - Mouse.ButtonUp += (sender, e) => HandleMouseClick(context, e); - Mouse.ButtonDown += (sender, e) => - { - if (!HandleMouseClick(context, e)) - { - if (e.Button == MouseButton.Left) - fractal.Pan(0.5f * (2.0f * e.X - Width) / Height, - 0.5f * (2.0f * e.Y - Height) / Height); - else if (e.Button == MouseButton.Right) - fractal.ZoomIn(1); - } - }; - /* Add AntTweakBar variables and events */ var configsBar = new Bar(context); @@ -399,10 +343,86 @@ private enum FractalPreset ExpIz, } + protected override void OnMouseDown(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + + if (HandleMouseClick(context, e)) { + return; + } + + if (e.Button == MouseButton.Left) + fractal.Pan(0.5f * (2.0f * e.X - Width) / Height, + 0.5f * (2.0f * e.Y - Height) / Height); + else if (e.Button == MouseButton.Right) + fractal.ZoomIn(1); + } + + protected override void OnMouseUp(MouseButtonEventArgs e) + { + base.OnMouseDown(e); + + if (HandleMouseClick(context, e)) { + return; + } + } + + protected override void OnMouseMove(MouseMoveEventArgs e) + { + base.OnMouseMove(e); + + if (context.HandleMouseMove(e.Position)) { + return; + } + } + + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + base.OnMouseWheel(e); + + if (context.HandleMouseWheel(e.Value)) { + return; + } + + fractal.ZoomIn(e.DeltaPrecise); + } + + protected override void OnKeyDown(KeyboardKeyEventArgs e) + { + base.OnKeyDown(e); + + if (HandleKeyDown(context, e)) { + return; + } + + if (e.Key == Key.Escape) { + Close(); + } + } + + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + base.OnKeyUp(e); + + if (HandleKeyUp(context, e)) { + return; + } + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + base.OnKeyPress(e); + + if (context.HandleKeyPress(e.KeyChar)) { + return; + } + } + protected override void OnResize(EventArgs e) { base.OnResize(e); fractal.Dimensions = ClientSize; + context.HandleResize(ClientSize); } protected override void OnRenderFrame(FrameEventArgs e) From 1e0f338ea4c9d327b3df7bfd00f0cd5da73ea818 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Sat, 6 Dec 2014 16:27:07 +1300 Subject: [PATCH 22/38] Added v0.5.0 milestones in readme. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index fd4713d..1fb9dff 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ +DEV BRANCH +========== + +v0.5.0 milestones: + + * fix input handling (see #4) + * implement a good StructVariable or delay to next release (not critical) + * check that the groups work properly, and maybe add a circular reference check + * document all low-level functions, check all native functions are there + AntTweakBar.NET 0.4.4 ===================== From e444b619961b58b4f5d4d1808daa4d8880bdfd6d Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Fri, 12 Dec 2014 13:25:58 +1300 Subject: [PATCH 23/38] Improved input handling. --- Library/Context.cs | 83 ++++++++++++++++++++++++---------------------- Library/Native.cs | 30 ++++++++++++++--- Sample/Program.cs | 2 +- 3 files changed, 70 insertions(+), 45 deletions(-) diff --git a/Library/Context.cs b/Library/Context.cs index d1ae5f1..23cce41 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -139,7 +139,25 @@ public bool HandleMouseClick(Tw.MouseAction action, Tw.MouseButton button) return Tw.MouseClick(action, button); } - private bool ignoreKeyPress = false; + /* I don't know if this input handling code is correct. It is actually NOT straightforward to + * convert from conventional KeyDown/KeyPressed/KeyUp events to AntTweakBar key presses. This + * may or may not be correct and was inspired from TwEventSFML.cpp, if you know how to fix it + * please issue a pull request or a patch, until then it looks satisfactory. Kind of. + */ + + private Tw.KeyModifiers lastModifiers; + private bool ignoreKeyPress; + + /// + /// All the special keys supported by AntTweakBar. + /// + private static IList SpecialKeys = new List() { + Tw.Key.Escape, Tw.Key.Return, Tw.Key.Tab, Tw.Key.Backspace, Tw.Key.PageUp, Tw.Key.PageDown, + Tw.Key.Up, Tw.Key.Down, Tw.Key.Left, Tw.Key.Right, Tw.Key.End, Tw.Key.Home, Tw.Key.Insert, + Tw.Key.Delete, Tw.Key.Space, Tw.Key.F1, Tw.Key.F2, Tw.Key.F3, Tw.Key.F4, Tw.Key.F5, + Tw.Key.F6, Tw.Key.F7, Tw.Key.F8, Tw.Key.F9, Tw.Key.F10, Tw.Key.F11, Tw.Key.F12, + Tw.Key.F13, Tw.Key.F14, Tw.Key.F15, + }; /// /// Notifies this context that a character has been typed. @@ -150,10 +168,14 @@ public bool HandleKeyPress(char character) Tw.SetCurrentWindow(Identifier); if (!ignoreKeyPress) { - return Tw.KeyPressed((int)character, Tw.KeyModifiers.None); - } else { - return false; + if (((character & 0xFF) < 32) && (character != 0) && ((character & 0xFF00) == 0)) { + return Tw.KeyPressed((char)((character & 0xFF) + 'a' - 1), Tw.KeyModifiers.Ctrl | lastModifiers); + } else { // this is supposed to handle the Ctrl+letter combination properly (somehow) + return Tw.KeyPressed((char)(character & 0xFF), Tw.KeyModifiers.None); + } } + + return (ignoreKeyPress = false); } /// @@ -163,23 +185,28 @@ public bool HandleKeyPress(char character) /// The key modifiers. public bool HandleKeyDown(Tw.Key key, Tw.KeyModifiers modifiers) { - // TODO - Tw.SetCurrentWindow(Identifier); + lastModifiers = modifiers; - var handled = Tw.KeyPressed((int)key, modifiers); - - if (handled) { - if (((int)key >= (int)'A') && ((int)key <= (int)'Z')) { - ignoreKeyPress = true; - } + if (SpecialKeys.Contains(key)) { + ignoreKeyPress = true; // special keys + return Tw.KeyPressed(key, modifiers); + } - if (key == Tw.Key.Space) { - ignoreKeyPress = true; + if (modifiers.HasFlag(Tw.KeyModifiers.Alt)) { + if ((key >= Tw.Key.A) && (key <= Tw.Key.Z)) { + ignoreKeyPress = true; // normal key shortcuts? + if (modifiers.HasFlag(Tw.KeyModifiers.Shift)) { + key = (Tw.Key)('A' + key - Tw.Key.A); + return Tw.KeyPressed(key, modifiers); + } else { + key = (Tw.Key)('a' + key - Tw.Key.A); + return Tw.KeyPressed(key, modifiers); + } } } - return handled; + return false; } /// @@ -189,34 +216,10 @@ public bool HandleKeyDown(Tw.Key key, Tw.KeyModifiers modifiers) /// The key modifiers. public bool HandleKeyUp(Tw.Key key, Tw.KeyModifiers modifiers) { + lastModifiers = Tw.KeyModifiers.None; return (ignoreKeyPress = false); } -#if false - - /// - /// Tests this context for whether a key press would be handled. - /// - /// The key character pressed. - public bool HandleKeyTest(char key) - { - Tw.SetCurrentWindow(Identifier); - return Tw.KeyTest((int)key, Tw.KeyModifiers.None); - } - - /// - /// Tests this context for whether a special key press would be handled. - /// - /// The key pressed. - /// The key modifiers pressed. - public bool HandleKeyTest(Tw.SpecialKey key, Tw.KeyModifiers modifiers) - { - Tw.SetCurrentWindow(Identifier); - return Tw.KeyTest((int)key, modifiers); - } - -#endif - /// /// The SFML event handler. /// diff --git a/Library/Native.cs b/Library/Native.cs index 0538983..517b441 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -453,9 +453,31 @@ public static bool MouseClick(MouseAction action, MouseButton button) /// The ASCII code of the pressed key, or one of the codes. /// One or a combination of the constants. /// Whether the key event has been handled by AntTweakBar. - public static bool KeyPressed(int key, KeyModifiers modifiers) + public static bool KeyPressed(char key, KeyModifiers modifiers) { - return NativeMethods.TwKeyPressed(key, modifiers); + return NativeMethods.TwKeyPressed((int)key, modifiers); + } + + /// + /// Call this function to inform AntTweakBar when a keyboard event occurs. + /// + /// The ASCII code of the pressed key, or one of the codes. + /// One or a combination of the constants. + /// Whether the key event has been handled by AntTweakBar. + public static bool KeyPressed(Tw.Key key, KeyModifiers modifiers) + { + return KeyPressed((char)key, modifiers); + } + + /// + /// This function checks if a key event would be processed but without processing it. This could be helpful to prevent bad handling report. + /// + /// The ASCII code of the pressed key, or one of the codes. + /// One or a combination of the constants. + /// Whether the key event would have been handled by AntTweakBar. + public static bool KeyTest(char key, KeyModifiers modifiers) + { + return NativeMethods.TwKeyTest((int)key, modifiers); } /// @@ -464,9 +486,9 @@ public static bool KeyPressed(int key, KeyModifiers modifiers) /// The ASCII code of the pressed key, or one of the codes. /// One or a combination of the constants. /// Whether the key event would have been handled by AntTweakBar. - public static bool KeyTest(int key, KeyModifiers modifiers) + public static bool KeyTest(Tw.Key key, KeyModifiers modifiers) { - return NativeMethods.TwKeyTest(key, modifiers); + return KeyTest((char)key, modifiers); } /// diff --git a/Sample/Program.cs b/Sample/Program.cs index 09a3bc3..3a9bcb5 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -139,7 +139,7 @@ public static int Main(String[] args) // Because OpenTK does not use an event loop, the native AntTweakBar library // has no provisions for directly handling user events. Therefore we need to - // convert polled OpenTK events to AntTweakBar events before handling them. + // convert any OpenTK events to AntTweakBar events before handling them. private static bool HandleMouseClick(Context context, MouseButtonEventArgs e) { From 797da95f2056fa9a4cac3d7e0cefd777f3e45a27 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Fri, 12 Dec 2014 13:46:02 +1300 Subject: [PATCH 24/38] Some documentation and style fixes. --- Library/Context.cs | 9 ++++++--- Library/Native.cs | 16 ++++++++-------- Library/Types.cs | 42 +++++++++++++++++++++--------------------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/Library/Context.cs b/Library/Context.cs index 23cce41..284e46d 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -162,7 +162,7 @@ public bool HandleMouseClick(Tw.MouseAction action, Tw.MouseButton button) /// /// Notifies this context that a character has been typed. /// - /// The character typed. + /// The character typed. public bool HandleKeyPress(char character) { Tw.SetCurrentWindow(Identifier); @@ -173,9 +173,11 @@ public bool HandleKeyPress(char character) } else { // this is supposed to handle the Ctrl+letter combination properly (somehow) return Tw.KeyPressed((char)(character & 0xFF), Tw.KeyModifiers.None); } + } else { + ignoreKeyPress = false; } - return (ignoreKeyPress = false); + return false; } /// @@ -217,7 +219,8 @@ public bool HandleKeyDown(Tw.Key key, Tw.KeyModifiers modifiers) public bool HandleKeyUp(Tw.Key key, Tw.KeyModifiers modifiers) { lastModifiers = Tw.KeyModifiers.None; - return (ignoreKeyPress = false); + ignoreKeyPress = false; + return false; } /// diff --git a/Library/Native.cs b/Library/Native.cs index 517b441..a8938f1 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -450,8 +450,8 @@ public static bool MouseClick(MouseAction action, MouseButton button) /// /// Call this function to inform AntTweakBar when a keyboard event occurs. /// - /// The ASCII code of the pressed key, or one of the codes. - /// One or a combination of the constants. + /// The ASCII code of the pressed key. + /// One or a combination of the constants. /// Whether the key event has been handled by AntTweakBar. public static bool KeyPressed(char key, KeyModifiers modifiers) { @@ -461,8 +461,8 @@ public static bool KeyPressed(char key, KeyModifiers modifiers) /// /// Call this function to inform AntTweakBar when a keyboard event occurs. /// - /// The ASCII code of the pressed key, or one of the codes. - /// One or a combination of the constants. + /// One of the codes. + /// One or a combination of the constants. /// Whether the key event has been handled by AntTweakBar. public static bool KeyPressed(Tw.Key key, KeyModifiers modifiers) { @@ -472,8 +472,8 @@ public static bool KeyPressed(Tw.Key key, KeyModifiers modifiers) /// /// This function checks if a key event would be processed but without processing it. This could be helpful to prevent bad handling report. /// - /// The ASCII code of the pressed key, or one of the codes. - /// One or a combination of the constants. + /// The ASCII code of the pressed key. + /// One or a combination of the constants. /// Whether the key event would have been handled by AntTweakBar. public static bool KeyTest(char key, KeyModifiers modifiers) { @@ -483,8 +483,8 @@ public static bool KeyTest(char key, KeyModifiers modifiers) /// /// This function checks if a key event would be processed but without processing it. This could be helpful to prevent bad handling report. /// - /// The ASCII code of the pressed key, or one of the codes. - /// One or a combination of the constants. + /// One of the codes. + /// One or a combination of the constants. /// Whether the key event would have been handled by AntTweakBar. public static bool KeyTest(Tw.Key key, KeyModifiers modifiers) { diff --git a/Library/Types.cs b/Library/Types.cs index f9bb953..dddf0a9 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -5,22 +5,22 @@ namespace AntTweakBar { /// - /// Specifies the possible color selection modes. + /// The possible color selection modes. /// public enum ColorMode { /// - /// Color selection is in RGB mode. + /// RGB color selection mode. /// RGB, /// - /// Color selection is in HLS (HSL) mode. + /// HLS (HSL) color selection mode. /// HLS, } /// - /// Specifies the possible (axis-aligned) axis orientations. + /// The possible (axis-aligned) axis orientations. /// public enum AxisOrientation { @@ -91,7 +91,7 @@ public static partial class Tw [In] IntPtr message); /// - /// Specifies the graphics API's AntTweakBar supports. + /// The graphics API's AntTweakBar supports. /// public enum GraphicsAPI { @@ -122,7 +122,7 @@ public enum GraphicsAPI } /// - /// Specifies the valid parameter value types to SetParam. + /// The valid parameter value types to SetParam. /// public enum ParamValueType { @@ -145,12 +145,12 @@ public enum ParamValueType } /// - /// Defines the maximum static string length. + /// The maximum static string length. /// internal const int MaxStringLength = 4096; /// - /// Specifies the different possible variable type, excluding enums and structs. + /// The different possible variable type, excluding enums and structs. /// public enum VariableType { @@ -245,7 +245,7 @@ public enum VariableType }; /// - /// Specifies information about a member of an AntTweakBar struct variable. + /// Information about a member of an AntTweakBar struct variable. /// public struct StructMemberInfo { @@ -302,7 +302,7 @@ public override String ToString() } /// - /// Specifies the possible mouse actions recognized by AntTweakBar. + /// The possible mouse actions recognized by AntTweakBar. /// public enum MouseAction { @@ -317,55 +317,55 @@ public enum MouseAction } /// - /// Specifies the possible mouse buttons recognized by AntTweakBar. + /// The possible mouse buttons recognized by AntTweakBar. /// public enum MouseButton { None = 0, /// - /// Represents the left mouse button. + /// The left mouse button. /// Left = 1, /// - /// Represents the middle mouse button. + /// The middle mouse button. /// Middle = 2, /// - /// Represents the right mouse button. + /// The right mouse button. /// Right = 3, } /// - /// Specifies the possible key modifiers recognized by AntTweakBar. + /// The possible key modifiers recognized by AntTweakBar. /// [Flags] public enum KeyModifiers { /// - /// Represents no key modifier. + /// No key modifier. /// None = 0x0000, /// - /// Represents the shift key modifier. + /// The shift key modifier. /// Shift = 0x0003, /// - /// Represents the ctrl key modifier. + /// The ctrl key modifier. /// Ctrl = 0x00c0, /// - /// Represents the alt key modifier. + /// The alt key modifier. /// Alt = 0x0100, /// - /// Represents the meta key modifier. + /// The meta key modifier. /// Meta = 0x0c00, } /// - /// Specifies the possible special keys recognized by AntTweakBar. + /// The possible keys recognized by AntTweakBar. /// public enum Key { From 7848e05ce1035319b7d1e4fe6ca503b460013b9c Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Fri, 12 Dec 2014 13:50:38 +1300 Subject: [PATCH 25/38] Removed some unused usings. --- Library/StringVariable.cs | 3 --- Library/StructVariable.cs | 1 - 2 files changed, 4 deletions(-) diff --git a/Library/StringVariable.cs b/Library/StringVariable.cs index 259b817..71af900 100644 --- a/Library/StringVariable.cs +++ b/Library/StringVariable.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; namespace AntTweakBar { diff --git a/Library/StructVariable.cs b/Library/StructVariable.cs index 38f3cd5..c4f9664 100644 --- a/Library/StructVariable.cs +++ b/Library/StructVariable.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; namespace AntTweakBar { From 5861c3ba3433b583a19798a751db30374455fd51 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Fri, 12 Dec 2014 18:16:49 +1300 Subject: [PATCH 26/38] Improved StructVariable. --- Library/StructVariable.cs | 39 ++++++++++++++++++++++++--------------- Library/Variable.cs | 4 ++-- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Library/StructVariable.cs b/Library/StructVariable.cs index c4f9664..922b35d 100644 --- a/Library/StructVariable.cs +++ b/Library/StructVariable.cs @@ -5,6 +5,10 @@ namespace AntTweakBar { + /// + /// A pseudo-variable acting as a container for a structured group of related variables. + /// + /// The type of this variable's value. public abstract class StructVariable : IValueVariable { /// @@ -38,28 +42,39 @@ public void OnChanged(EventArgs e) [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected IList variables; - public StructVariable(Bar bar, params IValueVariable[] variables) + /// + /// Creates a new struct variable in a given bar. + /// + /// The bar to create the struct variable in. + /// The initial value of the variable. + /// The set of member variables. + public StructVariable(Bar bar, T initialValue = default(T), params IValueVariable[] members) { if (bar == null) { throw new ArgumentNullException("bar"); - } else if (variables == null) { - throw new ArgumentNullException("variables"); - } else if (variables.Length == 0) { + } else if (members == null) { + throw new ArgumentNullException("members"); + } else if (members.Length == 0) { throw new ArgumentException("At least one variable."); - } else if (variables.Any((var) => (var.ParentBar != bar))) { + } else if (members.Any((var) => (var.ParentBar != bar))) { throw new ArgumentException("All variables must be in the same bar."); } - this.variables = new List(variables); - this.parentBar = bar; + variables = new List(members); + parentBar = bar; - foreach (var variable in this.variables) { + foreach (var variable in variables) { variable.Changed += (s, e) => { OnChanged(e); }; } + + Value = initialValue; } + /// + /// Gets or sets the group this variable represents. + /// public Group Group { get { return (variables.First() as Variable).Group; } @@ -73,15 +88,9 @@ public Group Group #region IDisposable - ~StructVariable() - { - Dispose(false); - } - public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } private void Dispose(bool disposing) @@ -114,7 +123,7 @@ protected void ThrowIfDisposed() public override String ToString() { - return String.Format("[StructVariable<{0}>: Label={1}, Value={2}]", typeof(T).Name, null /* TODO */, Value); + return String.Format("[StructVariable<{0}>: Group={1}, Value={2}]", typeof(T).Name, Group, Value); } } } diff --git a/Library/Variable.cs b/Library/Variable.cs index 5cbef73..baeede5 100644 --- a/Library/Variable.cs +++ b/Library/Variable.cs @@ -7,7 +7,7 @@ namespace AntTweakBar { /// - /// The interface implemented by all AntTweakBar variables. + /// Implemented by all AntTweakBar variables. /// public interface IVariable : IDisposable { @@ -18,7 +18,7 @@ public interface IVariable : IDisposable } /// - /// The interface implemented by all AntTweakBar value variables. + /// Implemented by all AntTweakBar value variables. /// public interface IValueVariable : IVariable { From 9a1e23d2f806535865f203b32b89a1fe595680d9 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Fri, 12 Dec 2014 18:17:10 +1300 Subject: [PATCH 27/38] Improved the sample's PolynomialVariable and ComplexVariable considerably. --- Sample/Polynomial.cs | 40 +++++++++++++++++-- Sample/Program.cs | 91 +++++++++++++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 33 deletions(-) diff --git a/Sample/Polynomial.cs b/Sample/Polynomial.cs index b132f07..43b4371 100644 --- a/Sample/Polynomial.cs +++ b/Sample/Polynomial.cs @@ -13,6 +13,7 @@ namespace Sample public class Polynomial : IEquatable { private readonly Dictionary terms; + private String repr; /// /// Creates a new zero polynomial. @@ -340,12 +341,39 @@ public override int GetHashCode() public override String ToString() { - return String.Join("+", terms.Keys.Where(p => terms[p] != Complex.Zero).OrderByDescending(x => x).Select(power => + if (repr != null) { + return repr; + } + + return String.Join(" + ", terms.Keys.Where(p => terms[p] != Complex.Zero).OrderByDescending(x => x).Select(power => { - return String.Format("{0}z^{1}", terms[power], power); + String exponent; + + if (power == 0) { + exponent = ""; + } else { + exponent = String.Format("z^{0}", power); + } + + if (terms[power].Real == 0) { + return String.Format("{0}{1}", ParseDouble(terms[power].Imaginary), exponent); + } else if (terms[power].Imaginary == 0) { + return String.Format("{0}{1}", ParseDouble(terms[power].Real), exponent); + } else { + return String.Format("({0} + {1}i){2}", ParseDouble(terms[power].Real), ParseDouble(terms[power].Imaginary), exponent); + } })); } + private String ParseDouble(Double x) + { + if (x != 1) { + return "(" + x.ToString() + ")"; + } else { + return ""; + } + } + #endregion #region Parsing Code @@ -353,14 +381,20 @@ public override String ToString() /// /// Parses an expression into a complex polynomial. /// - public static Polynomial Parse(String str, IDictionary symbols) + public static Polynomial Parse(String str, IDictionary symbols = null) { + if (symbols == null) { + symbols = new Dictionary(); + } + try { + var inputString = str; // for repr field str = Regex.Replace(str, @"\s+", ""); int pos; bool negate = str[0] == '-'; if (negate) str = str.Substring(1); var polynomial = new Polynomial(); + polynomial.repr = inputString; do { diff --git a/Sample/Program.cs b/Sample/Program.cs index 3a9bcb5..9865b90 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -17,11 +17,13 @@ namespace Sample /// This variable will hold a polynomial (if an invalid formula /// is entered by the user, it will simply refuse to accept it). /// - class PolynomialVariable + class PolynomialVariable : IValueVariable { // Used to hold optional symbols used during polynomial parsing private readonly IDictionary symbols = new Dictionary(); + public IDictionary Symbols { get { return symbols; } } + /// /// Gets or sets a polynomial symbol. /// @@ -38,64 +40,93 @@ class PolynomialVariable } } + public Bar ParentBar { get { return polyString.ParentBar; } } + + public event EventHandler Changed + { + add { polyString.Changed += value; } + remove { polyString.Changed -= value; } + } + + public void OnChanged(EventArgs e) + { + polyString.OnChanged(e); + } + /// /// The actual backing variable. /// - public StringVariable PolyString { get; set; } + private StringVariable polyString { get; set; } - public PolynomialVariable(Bar bar, String poly, String def = null) + public PolynomialVariable(Bar bar, Polynomial poly, String def = null) { - PolyString = new StringVariable(bar, poly, def); - PolyString.Validating += (s, e) => { e.Valid = (Polynomial.Parse(e.Value, symbols) != null); }; + polyString = new StringVariable(bar, poly.ToString(), def); + polyString.Validating += (s, e) => { + e.Valid = (Polynomial.Parse(e.Value, symbols) != null); + }; + } + + public String Label + { + get { return polyString.Label; } + set { polyString.Label = value; } } - public Polynomial Polynomial + public Polynomial Value { - get { return Polynomial.Parse(PolyString.Value, symbols); } + get { return Polynomial.Parse(polyString.Value, symbols); } + set { polyString.Value = value.ToString(); } + } + + public void Dispose() + { + polyString.Dispose(); } } class ComplexVariable : StructVariable { - public ComplexVariable(Bar bar, Complex initialValue) : base(bar, new DoubleVariable(bar, 0, "label=Real"), new DoubleVariable(bar, 0, "label=Imaginary")) + private DoubleVariable re { get { return (variables[0] as DoubleVariable); } } + private DoubleVariable im { get { return (variables[1] as DoubleVariable); } } + + public ComplexVariable(Bar bar, Complex initialValue) + : base(bar, initialValue, + new DoubleVariable(bar, 0, "label=Real"), + new DoubleVariable(bar, 0, "label=Imaginary")) { - Value = initialValue; } public override Complex Value { get { - return new Complex( - (variables[0] as DoubleVariable).Value, - (variables[1] as DoubleVariable).Value - ); + return new Complex(re.Value, im.Value); } set { - (variables[0] as DoubleVariable).Value = value.Real; - (variables[1] as DoubleVariable).Value = value.Imaginary; + re.Value = value.Real; + im.Value = value.Imaginary; } } public Double Step { - get { return (variables[0] as DoubleVariable).Step; } + get { return re.Step; } set { - (variables[0] as DoubleVariable).Step = value; - (variables[1] as DoubleVariable).Step = value; + re.Step = value; + im.Step = value; } } public Double Precision { - get { return (variables[0] as DoubleVariable).Precision; } + get { return re.Precision; } set { - (variables[0] as DoubleVariable).Precision = value; - (variables[1] as DoubleVariable).Precision = value; + re.Precision = value; + im.Precision = value; } } } @@ -271,30 +302,30 @@ protected override void OnLoad(EventArgs _) fractalBar.Position = new Point(Width - 500 - 20, 20); fractalBar.Size = new Size(500, 150); - var poly = new PolynomialVariable(fractalBar, "z^3 - 1"); + var poly = new PolynomialVariable(fractalBar, Polynomial.Parse("z^3 - 1")); var preset = new EnumVariable(fractalBar, FractalPreset.Cubic); - poly.PolyString.Changed += delegate { fractal.Polynomial = poly.Polynomial; }; - poly.PolyString.Label = "Equation"; + poly.Changed += delegate { fractal.Polynomial = poly.Value; }; + poly.Label = "Equation"; preset.Label = "Presets"; preset.Changed += delegate { switch (preset.Value) { case FractalPreset.Cubic: - poly.PolyString.Value = "z^3 - 1"; + poly.Value = Polynomial.Parse("z^3 - 1", poly.Symbols); break; case FractalPreset.OtherCubic: - poly.PolyString.Value = "z^3 - 2z + 2"; + poly.Value = Polynomial.Parse("z^3 - 2z + 2", poly.Symbols); break; case FractalPreset.SineTaylor: - poly.PolyString.Value = "z - 1/6z^3 + 1/120z^5 - 1/5040z^7 + 1/362880z^9"; + poly.Value = Polynomial.Parse("z - 1/6z^3 + 1/120z^5 - 1/5040z^7 + 1/362880z^9", poly.Symbols); break; case FractalPreset.ExpIz: - poly.PolyString.Value = "1 + iz - 1/2z^2 + (1/6i)z^3"; + poly.Value = Polynomial.Parse("1 + iz - 1/2z^2 + (1/6i)z^3", poly.Symbols); break; } - fractal.Polynomial = poly.Polynomial; + fractal.Polynomial = poly.Value; }; /* This is where you can use the Changed event to great advantage: we are @@ -316,7 +347,7 @@ protected override void OnLoad(EventArgs _) symbolVar.Changed += delegate { poly[symbolVar.Label] = symbolVar.Value; // update symbol - fractal.Polynomial = poly.Polynomial; // update fractal + fractal.Polynomial = poly.Value; // update fractal }; // also add the symbol initially, so that it exists on startup From b8fcb13f57244fa6e0c494bdc210e2d4f295845b Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Tue, 16 Dec 2014 15:56:28 +1300 Subject: [PATCH 28/38] Some improvements to StructVariable and ComplexVariable. --- Library/StructVariable.cs | 5 +--- Sample/Program.cs | 62 ++++++++++++++++----------------------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/Library/StructVariable.cs b/Library/StructVariable.cs index 922b35d..35cdd07 100644 --- a/Library/StructVariable.cs +++ b/Library/StructVariable.cs @@ -46,9 +46,8 @@ public void OnChanged(EventArgs e) /// Creates a new struct variable in a given bar. /// /// The bar to create the struct variable in. - /// The initial value of the variable. /// The set of member variables. - public StructVariable(Bar bar, T initialValue = default(T), params IValueVariable[] members) + public StructVariable(Bar bar, params IValueVariable[] members) { if (bar == null) { throw new ArgumentNullException("bar"); @@ -68,8 +67,6 @@ public StructVariable(Bar bar, T initialValue = default(T), params IValueVariabl OnChanged(e); }; } - - Value = initialValue; } /// diff --git a/Sample/Program.cs b/Sample/Program.cs index 9865b90..fe8b62a 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -17,7 +17,7 @@ namespace Sample /// This variable will hold a polynomial (if an invalid formula /// is entered by the user, it will simply refuse to accept it). /// - class PolynomialVariable : IValueVariable + sealed class PolynomialVariable : IValueVariable { // Used to hold optional symbols used during polynomial parsing private readonly IDictionary symbols = new Dictionary(); @@ -84,49 +84,50 @@ public void Dispose() } } - class ComplexVariable : StructVariable + sealed class ComplexVariable : StructVariable { - private DoubleVariable re { get { return (variables[0] as DoubleVariable); } } - private DoubleVariable im { get { return (variables[1] as DoubleVariable); } } + private DoubleVariable Re { get { return (variables[0] as DoubleVariable); } } + private DoubleVariable Im { get { return (variables[1] as DoubleVariable); } } public ComplexVariable(Bar bar, Complex initialValue) - : base(bar, initialValue, - new DoubleVariable(bar, 0, "label=Real"), - new DoubleVariable(bar, 0, "label=Imaginary")) + : base(bar, new DoubleVariable(bar, initialValue.Real), + new DoubleVariable(bar, initialValue.Imaginary)) { + Re.Label = "Real"; + Im.Label = "Imaginary"; } public override Complex Value { get { - return new Complex(re.Value, im.Value); + return new Complex(Re.Value, Im.Value); } set { - re.Value = value.Real; - im.Value = value.Imaginary; + Re.Value = value.Real; + Im.Value = value.Imaginary; } } public Double Step { - get { return re.Step; } + get { return Re.Step; } set { - re.Step = value; - im.Step = value; + Re.Step = value; + Im.Step = value; } } public Double Precision { - get { return re.Precision; } + get { return Re.Precision; } set { - re.Precision = value; - im.Precision = value; + Re.Precision = value; + Im.Precision = value; } } } @@ -183,7 +184,7 @@ private static bool HandleMouseClick(Context context, MouseButtonEventArgs e) } } - private static bool HandleKeyDown(Context context, KeyboardKeyEventArgs e) + private static bool HandleKeyInput(Context context, KeyboardKeyEventArgs e, bool down) { var modifiers = Tw.KeyModifiers.None; if (e.Modifiers.HasFlag(KeyModifiers.Alt)) @@ -194,24 +195,11 @@ private static bool HandleKeyDown(Context context, KeyboardKeyEventArgs e) modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Control]; if (Tw.Mappings.OpenTK.Keys.ContainsKey((int)e.Key)) { - return context.HandleKeyDown(Tw.Mappings.OpenTK.Keys[(int)e.Key], modifiers); - } else { - return false; - } - } - - private static bool HandleKeyUp(Context context, KeyboardKeyEventArgs e) - { - var modifiers = Tw.KeyModifiers.None; - if (e.Modifiers.HasFlag(KeyModifiers.Alt)) - modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Alt]; - if (e.Modifiers.HasFlag(KeyModifiers.Shift)) - modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Shift]; - if (e.Modifiers.HasFlag(KeyModifiers.Control)) - modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Control]; - - if (Tw.Mappings.OpenTK.Keys.ContainsKey((int)e.Key)) { - return context.HandleKeyUp(Tw.Mappings.OpenTK.Keys[(int)e.Key], modifiers); + if (down) { + return context.HandleKeyDown(Tw.Mappings.OpenTK.Keys[(int)e.Key], modifiers); + } else { + return context.HandleKeyUp(Tw.Mappings.OpenTK.Keys[(int)e.Key], modifiers); + } } else { return false; } @@ -422,7 +410,7 @@ protected override void OnKeyDown(KeyboardKeyEventArgs e) { base.OnKeyDown(e); - if (HandleKeyDown(context, e)) { + if (HandleKeyInput(context, e, true)) { return; } @@ -435,7 +423,7 @@ protected override void OnKeyUp(KeyboardKeyEventArgs e) { base.OnKeyUp(e); - if (HandleKeyUp(context, e)) { + if (HandleKeyInput(context, e, false)) { return; } } From e28c812b3e5ff716d826df25a08a19b8544021d1 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 14:50:53 +1300 Subject: [PATCH 29/38] Upgrading to v0.5.0. --- Library/Properties/AssemblyInfo.cs | 4 +- README.md | 99 +++--------------------------- 2 files changed, 9 insertions(+), 94 deletions(-) diff --git a/Library/Properties/AssemblyInfo.cs b/Library/Properties/AssemblyInfo.cs index e6f2177..6e5d228 100644 --- a/Library/Properties/AssemblyInfo.cs +++ b/Library/Properties/AssemblyInfo.cs @@ -33,5 +33,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("0.4.4.0")] -[assembly: AssemblyFileVersion("0.4.4.0")] +[assembly: AssemblyVersion("0.5.0.0")] +[assembly: AssemblyFileVersion("0.5.0.0")] diff --git a/README.md b/README.md index 1fb9dff..9e53208 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,4 @@ -DEV BRANCH -========== - -v0.5.0 milestones: - - * fix input handling (see #4) - * implement a good StructVariable or delay to next release (not critical) - * check that the groups work properly, and maybe add a circular reference check - * document all low-level functions, check all native functions are there - -AntTweakBar.NET 0.4.4 +AntTweakBar.NET 0.5.0 ===================== AntTweakBar.NET is an MIT-licensed C# wrapper for Philippe Decaudin's [AntTweakBar](http://anttweakbar.sourceforge.net) C/C++ GUI library. It allows C# developers to enhance their tech demos or games with an easy-to-use graphical widget for modifying application parameters in realtime. AntTweakBar.NET offers a high-level interface to the widget which will feel natural to any C# programmer, and also provides access to exception-safe bindings to the native AntTweakBar calls for those who might want them. @@ -23,7 +13,7 @@ The AntTweakBar.NET wrapper is distributed under the MIT license, while AntTweak Quick Start ----------- -You must obtain and install the native AntTweakBar library itself from its [SourceForge page](http://anttweakbar.sourceforge.net/doc/tools:anttweakbar:download), if you haven't already. For Windows, download the appropriate prebuilt DLL's and install them on your system or as third party libraries in your C# project. For Linux, simply `make && make install` as usual. Then add the `AntTweakBar.NET.dll` assembly in your project, either by compiling it from the repository or retrieving it from [NuGet](https://www.nuget.org/packages/AntTweakBar.NET/). You're good to go! +You must obtain and install the native AntTweakBar library itself from its [SourceForge page](http://anttweakbar.sourceforge.net/doc/tools:anttweakbar:download), if you haven't already. For Windows, download the appropriate prebuilt DLL's and install them on your system or as third party libraries in your C# project. For Linux, simply `make && make install` as usual (note: the 64-bit Windows DLL is called AntTweakBar64.dll, remove the "64" prefix). Then add the `AntTweakBar.NET.dll` assembly in your project, either by compiling it from the repository or retrieving it from [NuGet](https://www.nuget.org/packages/AntTweakBar.NET/). You're good to go! The AntTweakBar.NET high-level interface is divided into four main concepts: contexts, bars, variables and groups. @@ -75,6 +65,8 @@ The preferred way of doing event handling is by using the `Handle*()` events, wh In general you *do* want to keep references to contexts, because you actually do need to destroy them when you close your windows. The different AntTweakBar.NET classes implement the `IDisposable` interface. When you dispose a bar, all variables inside it are implicitly disposed. When you dispose a context, all bars inside it are implicitly disposed. In other words, it is sufficient to dispose the contexts you create. It is very important to note that you must dispose the last context **before** terminating your graphics API. A symptom of failing to do this is an exception on shutdown pointing to the `Tw.Terminate()` function. Critically, this means you cannot just leave the contexts to be garbage-collected, as it will probably be too late by the time they are. This should not be a problem in most sensible implementations. +For more information, make sure to check out the [wiki](wiki) (it's not finished, but already has some helpful content). + Notes on the Sample ------------------- @@ -84,94 +76,15 @@ This repository contains a sample, among other things, which is intended to show Screenshot of the sample program

-Advanced Usage --------------- - - - **Bar/variable property scripting** - - You can script the different properties of your bars or variables from e.g. a text file using the `SetDefinition` method. This method takes a definition string containing your parameters. For your convenience, there is an optional `def` parameter in the constructor which automatically calls it as well. This method should be used e.g. as: - - ```csharp - myVariable.SetDefinition("label='New Label' readonly=true"); - myBar.SetDefinition("contained=true visible=false"); - ``` - - This definition string follows the same format as documented on the AntTweakBar website under `TwDefine`, except it should not contain the variable's name, as you don't know what it is (it is automatically filled in by the method). - - - **Using groups** - - You can create a group as follows for instance: - - ```csharp - var myGroup = new Group(myBar, "A group", myVar); - /* or */ - var myGroup = new Group(myBar); - myVar.Group = myGroup; - myGroup.Label = "A group"; - ``` - - You can nest groups into groups by using the `Parent` property of groups, and you can change their labels (so you can have two groups with the same label if you want). Note `null` is a valid group which represents the root of the bar (i.e. no group). There are three gotchas to watch out for when using groups: - - \* A group only exists as long as there are variables under said group. This means that until you assign a variable to a group, you cannot modify the group's label or any of its properties. As a result, the `Group` class supports two kinds of initializations: lazy initialization, where you can create a non-existent group, assign it to a bunch of variables (which truly creates it) and configure it afterwards, or direct initialization, where the constructor takes a collection of initial variables to put under the group. Use whichever one is more convenient for you. - - \* A group ceases to exist as soon as there are no more variables under said group. This is not a big problem, it's just important to keep that in mind when dynamically creating variables as it may invalidate `Group` instances you are holding. - - \* Take care when putting groups into groups: if you put group A into group B, and then put group B into group A, both groups and all variables contained in them will be destroyed (this is done automatically by the native library). In general, avoid circular group references and use the `null` group to break such circular references. - - - **Defining custom variables** - - All the classes in the wrapper are sealed. In most cases you should favor composing variables together to extend their functionality, as done for instance in the sample, where a complex variable type is created by composing two `DoubleVariable` instances, and a polynomial variable type is implemented by adding custom validation logic to a `StringVariable` to make it only accept polynomial formulas. In general, the `Validating` event can be used to introduce additional requirements on the contents of the variable. The variable's value will be changed if and only if it passes validation by *all* event handlers attached to the variable's `Validating` event. - - If user input fails validation, the variable will gracefully revert to its previous value. On the other hand, if you manually try to set an invalid value from code, it will throw an `ArgumentException`. The following example shows how to make the value of `myVar` be a multiple of five: - - ```csharp - myVar.Validating += (s, e) => { e.Valid = (e.Value % 5 == 0); }; /* for IntVariable */ - ``` - - You must refer to `e.Value` (or `e.R`, `e.X`, etc. as appropriate) to perform validation, as the variable's value has not yet been updated when the validation handlers are called. Note you can of course refer to external objects in your handler to implement context-sensitive validation logic. Most variables already have built-in validators, for instance numeric variables validate against their `Min` and `Max` properties, `StringVariable` rejects null strings, etc. - - - **Defining struct variables** - - Struct variables as defined by AntTweakBar are not yet available in AntTweakBar.NET *per se*. However, you can currently emulate this functionality by composing together the different variables in said struct and putting them all in the same group. This group is then possibly nested into another group using the bar's `MoveGroup` method. This is somewhat awkward, and there may be first-class support for this type of aggregate variable in a future version. Also see the `Complex` type in the sample, which represents a complex number as two double variables (but it's not a great example, as it was written for a very early AntTweakBar.NET version, it will be improved eventually) - - - **Error handling** - - All AntTweakBar errors will be translated into `AntTweakBarException` instances. But you can also intercept errors via the `Tw.Error` event. It is probably not too useful to reason on the error messages received, but you can use this to log them, for example. - - - **More descriptive enums** - - By default an `EnumVariable` will graphically display the name of the enum value as defined in your code. You can give it a custom name or summary by tagging your enum values with a `DescriptionAttribute`, it will be picked up by AntTweakBar.NET and displayed instead of the raw enum name. - - - **Multiple windows** - - Each different window you want AntTweakBar to use should have a `Context`, and you should handle events and call the context's `Draw` method as needed on each window. That's really all there is to it. Internally, AntTweakBar directly draws into whatever render target is active, so multiple windows are naturally handled in both OpenGL and DirectX. The wrapper takes care of all necessary context-switching. - - - **AntTweakBar bindings** - - The bindings are in the `AntTweakBar.Tw` static class. For the most part you cannot interfere with the wrapper's operation with them since the wrapper does not expose its internal AntTweakBar pointers and identifiers for integrity reasons, so it is discouraged to use them to try and subvert the high-level wrapper. You are however encouraged to use them directly if you don't want to or can't use the high-level classes for whatever reason. Have fun! - - - **Exception safety** - - You should avoid throwing exceptions from within delegates subscribed to `Changed`, `Click` and `Validate` events. The reason for this is because they can be called from native code, and the results of throwing a managed exception inside an unmanaged callback is implementation-defined. It is recommended instead to log any exception and safely return. - - - **Thread safety** - - Unfortunately, several AntTweakBar functions are **not** thread safe due to the global state involved in switching AntTweakBar windows/contexts. The only code which has been specifically written to be thread-safe is the constructor (and to some extent the `Dispose` method) of the `Context` class, in order to avoid double initialization/finalization of the AntTweakBar library. In general, assume no function is thread-safe, just like most GUI frameworks. - Contribute ---------- Any issues or pull requests are welcome, I especially need help with verifying multi-window support, thread safety, and OS X testing, but any contribution is greatly appreciated. Thanks to *Ilkka Jahnukainen* for helping in testing AntTweakBar.NET throughout its ongoing development and providing valuable feedback to guide its design. -**Todo**: - - * more/better unit tests - * check it works on OS X - Changelog --------- -Next release: +16 December 2014 (v0.5.0): - changed `Tw.WindowSize` to accept sizes of (0, 0) to allow AntTweakBar resource cleanup (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) - added `ReleaseResources` and `ResetResources` methods to the `Context` class (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) @@ -179,6 +92,8 @@ Next release: - added TwDefineEnum and TwDefineStruct native functions and a DefineEnum low-level wrapper - various miscellaneous fixes and improvements to the Sample - added `Group` class and improved code relating to variable groups + - added `StructVariable` abstract class + - improved input handling (see [#4](https://github.com/TomCrypto/AntTweakBar.NET/issues/4)) 28 November 2014 (v0.4.4) From bea636b9cf19490e65af482cd3613e3ad0875969 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 14:54:17 +1300 Subject: [PATCH 30/38] Updated readme. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e53208..89def35 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The AntTweakBar.NET high-level interface is divided into four main concepts: con - **Variable**: This is the base class from which all other variable types (like `IntVariable` or `StringVariable`) descend from. Just like the `Bar` class, it and its descendants have plenty of properties you can modify to tweak the variable's behavior and graphical appearance. In addition, value variables hold a value property which can be set graphically by the user and can be read on the fly by your code, this is the `Value` property for simple types (like `IntVariable`) or e.g. the `X`, `Y`, `Z` properties for the `VectorVariable`. They also have a `Changed` event to be notified when the user changes the variable's value. The `Button` variable type has a `Clicked` event instead. Each variable belongs to a bar passed to its constructor. -- **Group**: These are used to put a set of variables together in the bar for easy access, you can open (expand) or close (collapse) groups, and can put groups into groups for a hierarchical organization. Please see "Using groups" in the advanced usage section below to find out how to use them. +- **Group**: These are used to put a set of variables together in the bar for easy access, you can open (expand) or close (collapse) groups, and can put groups into groups for a hierarchical organization. Please see the "groups" page in the [wiki](https://github.com/TomCrypto/AntTweakBar.NET/wiki) to find out how to use them. The first context created should be passed the graphics API you are using, which is some version of OpenGL or DirectX. For DirectX, you must also pass a pointer to the native device, which should be available from the graphics framework you are using somehow (for instance, for SharpDX, use `SharpDX.Direct3D11.Device.NativePointer`). @@ -65,7 +65,7 @@ The preferred way of doing event handling is by using the `Handle*()` events, wh In general you *do* want to keep references to contexts, because you actually do need to destroy them when you close your windows. The different AntTweakBar.NET classes implement the `IDisposable` interface. When you dispose a bar, all variables inside it are implicitly disposed. When you dispose a context, all bars inside it are implicitly disposed. In other words, it is sufficient to dispose the contexts you create. It is very important to note that you must dispose the last context **before** terminating your graphics API. A symptom of failing to do this is an exception on shutdown pointing to the `Tw.Terminate()` function. Critically, this means you cannot just leave the contexts to be garbage-collected, as it will probably be too late by the time they are. This should not be a problem in most sensible implementations. -For more information, make sure to check out the [wiki](wiki) (it's not finished, but already has some helpful content). +For more information, make sure to check out the [wiki](https://github.com/TomCrypto/AntTweakBar.NET/wiki) (it's not finished, but already has some helpful content). Notes on the Sample ------------------- From ee733859cb80a7a6a3132277bd0aa623ff2271c6 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 15:39:23 +1300 Subject: [PATCH 31/38] Added ListVariable. --- Library/Library.csproj | 1 + Library/ListVariable.cs | 192 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 Library/ListVariable.cs diff --git a/Library/Library.csproj b/Library/Library.csproj index 6e20d9b..fa17c0a 100644 --- a/Library/Library.csproj +++ b/Library/Library.csproj @@ -48,6 +48,7 @@ + diff --git a/Library/ListVariable.cs b/Library/ListVariable.cs new file mode 100644 index 0000000..1384c68 --- /dev/null +++ b/Library/ListVariable.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace AntTweakBar +{ + /// + /// Helper class for the ListVariable items. + /// + public class TupleList : List> + { + public void Add(T1 item, T2 item2) + { + Add(new Tuple(item, item2)); + } + } + + public sealed class ListValidationEventArgs : EventArgs + { + /// + /// Whether to accept this list value. + /// + public bool Valid { get; set; } + public T Value { get; private set; } + + public ListValidationEventArgs(T value) + { + Value = value; + } + } + + /// + /// An AntTweakBar variable which can hold a value from a list. + /// + public sealed class ListVariable : Variable, IValueVariable + { + /// + /// Occurs when the user changes this variable's value. + /// + public event EventHandler Changed; + + /// + /// Occurs when the new value of this variable is validated. + /// + public event EventHandler> Validating; + + /// + /// Raises the Changed event. + /// + public void OnChanged(EventArgs e) + { + ThrowIfDisposed(); + + if (Changed != null) { + Changed(this, e); + } + } + + /// + /// Gets or sets the value of this variable. + /// + public T Value + { + get { ThrowIfDisposed(); return value; } + set { ValidateAndSet(value); } + } + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private T value; + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IDictionary itemToDescr = new Dictionary(); + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IDictionary descrToItem = new Dictionary(); + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private IList descrIndices = new List(); + + /// + /// Gets this list variable's type. + /// + public Tw.VariableType Type { get { ThrowIfDisposed(); return type; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private Tw.VariableType type; + + /// + /// Initialization delegate, which creates the list variable. + /// + private static void InitListVariable(Variable var, String id, IList> items) + { + var it = var as ListVariable; + + Variable.SetCallbacks.Add(id, new Tw.SetVarCallback(it.SetCallback)); + Variable.GetCallbacks.Add(id, new Tw.GetVarCallback(it.GetCallback)); + + if (items.Count != items.Select(kv => kv.Item1).Distinct().Count()) { + throw new ArgumentException("Duplicate items in list."); + } + + var descr = new Dictionary(); + int t = 0; + + foreach (var kv in items) { + descr.Add(t++, kv.Item2); + } + + Tw.AddVarCB(var.ParentBar.Pointer, id, + it.type = Tw.DefineEnum(Guid.NewGuid().ToString(), descr), + Variable.SetCallbacks[id], + Variable.GetCallbacks[id], + IntPtr.Zero, null); + } + + /// + /// Creates a new list variable in a given bar. + /// + /// The bar to create the list variable in. + /// The list of items and their descriptions. + /// The initial value of the variable. + /// An optional definition string for the new variable. + public ListVariable(Bar bar, IList> items, T initialValue, String def = null) + : base(bar, (var, id) => InitListVariable(var, id, items), def) + { + foreach (var kv in items) { + itemToDescr.Add(kv.Item1, kv.Item2); + descrToItem.Add(kv.Item2, kv.Item1); + descrIndices.Add(kv.Item2); + } + + Validating += (s, e) => { e.Valid = itemToDescr.ContainsKey(e.Value); }; + ValidateAndSet(initialValue); + } + + /// + /// Checks if this variable can hold this value. + /// + private bool IsValid(T value) + { + ThrowIfDisposed(); + + return !Validating.GetInvocationList().Select(h => { + var check = new ListValidationEventArgs(value); + h.DynamicInvoke(new object[] { this, check }); + return !check.Valid; + }).Any(failed => failed); + } + + /// + /// Tries to set this variable's value, validating it. + /// + private void ValidateAndSet(T value) + { + if (!IsValid(value)) { + throw new ArgumentException("Invalid variable value."); + } else { + this.value = value; + } + } + + /// + /// Called by AntTweakBar when the user changes the variable's value. + /// + private void SetCallback(IntPtr pointer, IntPtr clientData) + { + T data = descrToItem[descrIndices[Marshal.ReadInt32(pointer)]]; + + if (IsValid((T)(object)data)) + { + bool changed = !value.Equals(data); + value = data; + + if (changed) { + OnChanged(EventArgs.Empty); + } + } + } + + /// + /// Called by AntTweakBar when AntTweakBar needs the variable's value. + /// + private void GetCallback(IntPtr pointer, IntPtr clientData) + { + Marshal.WriteInt32(pointer, descrIndices.IndexOf(itemToDescr[Value])); + } + + public override String ToString() + { + return String.Format("[ListVariable<{0}>: Label={1}, Value={2}]", typeof(T).Name, Label, Value); + } + } +} From 2e95ff39a8b1fb56830be01c3782905ee02a3c30 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 15:40:48 +1300 Subject: [PATCH 32/38] Updated readme. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 89def35..f47350a 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Any issues or pull requests are welcome, I especially need help with verifying m Changelog --------- -16 December 2014 (v0.5.0): +?? December 2014 (v0.5.0): - changed `Tw.WindowSize` to accept sizes of (0, 0) to allow AntTweakBar resource cleanup (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) - added `ReleaseResources` and `ResetResources` methods to the `Context` class (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) @@ -94,6 +94,7 @@ Changelog - added `Group` class and improved code relating to variable groups - added `StructVariable` abstract class - improved input handling (see [#4](https://github.com/TomCrypto/AntTweakBar.NET/issues/4)) + - added `ListVariable` variable type 28 November 2014 (v0.4.4) From 2ad8255a91b80f787465e3bd70e610f302e27c01 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 16:04:29 +1300 Subject: [PATCH 33/38] Added a few more bar properties. --- Library/Bar.cs | 54 ++++++++++++++++++++++++++++++++++++++++++++++ Library/Context.cs | 10 +++++++++ Library/Types.cs | 34 +++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/Library/Bar.cs b/Library/Bar.cs index 703a14b..956bd2e 100644 --- a/Library/Bar.cs +++ b/Library/Bar.cs @@ -107,6 +107,15 @@ public byte Alpha set { Tw.SetParam(Pointer, null, "alpha", value); } } + /// + /// Gets or sets this bar's text color. + /// + public BarTextColor TextColor + { + get { return Tw.GetStringParam(Pointer, null, "text") == "dark" ? BarTextColor.Dark : BarTextColor.Light; } + set { Tw.SetParam(Pointer, null, "text", value == BarTextColor.Dark ? "dark" : "light"); } + } + /// /// Gets or sets this bar's position. /// @@ -125,6 +134,33 @@ public Size Size set { Tw.SetParam(Pointer, null, "size", value); } } + /// + /// Gets or sets the width in pixels of this bar's value column. Set to zero for auto-fit. + /// + public Int32 ValueColumnWidth + { + get { return Math.Max(0, Tw.GetIntParam(Pointer, null, "valueswidth")[0]); } + set { Tw.SetParam(Pointer, null, "valueswidth", value == 0 ? "fit" : value.ToString()); } + } + + /// + /// Gets or sets this bar's refresh rate in seconds. + /// + public Int32 RefreshRate + { + get { return Tw.GetIntParam(Pointer, null, "refresh")[0]; } + set { Tw.SetParam(Pointer, null, "refresh", value); } + } + + /// + /// Gets or sets the alignment of buttons in this bar. + /// + public BarButtonAlignment ButtonAlignment + { + get { return (BarButtonAlignment)Enum.Parse(typeof(BarButtonAlignment), Tw.GetStringParam(Pointer, null, "buttonalign"), true); } + set { Tw.SetParam(Pointer, null, "buttonalign", value.ToString().ToLower()); } + } + /// /// Gets or sets whether this bar can be iconified by the user. /// @@ -170,6 +206,24 @@ public Boolean Visible set { Tw.SetParam(Pointer, null, "visible", value); } } + /// + /// Gets or sets whether this bar is always at the front. + /// + public Boolean AlwaysFront + { + get { return Tw.GetBooleanParam(Pointer, null, "alwaystop"); } + set { Tw.SetParam(Pointer, null, "alwaystop", value); } + } + + /// + /// Gets or sets whether this bar is always at the back. + /// + public Boolean AlwaysBack + { + get { return Tw.GetBooleanParam(Pointer, null, "alwaysbottom"); } + set { Tw.SetParam(Pointer, null, "alwaysbottom", value); } + } + /// /// Brings this bar in front of all others. /// diff --git a/Library/Context.cs b/Library/Context.cs index 284e46d..332666c 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -264,6 +264,16 @@ public void ShowHelpBar(Boolean visible) Tw.Define("TW_HELP visible=" + (visible ? "true" : "false")); } + /// + /// Sets whether to draw or clip overlapping bars. + /// + /// If false, clips the contents of overlapping region (default). + public void SetOverlap(Boolean overlap) + { + Tw.SetCurrentWindow(Identifier); // applies to this context + Tw.Define("GLOBAL overlap=" + (overlap ? "true" : "false")); + } + #endregion #region IEnumerable diff --git a/Library/Types.cs b/Library/Types.cs index dddf0a9..bb63529 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -4,6 +4,40 @@ namespace AntTweakBar { + /// + /// The possible bar text colors. + /// + public enum BarTextColor + { + /// + /// Darker text. + /// + Dark, + /// + /// Lighter text. + /// + Light, + } + + /// + /// The possible button alignments in a bar. + /// + public enum BarButtonAlignment + { + /// + /// Left-aligned. + /// + Left, + /// + /// Center-aligned. + /// + Center, + /// + /// Right-aligned. + /// + Right, + } + /// /// The possible color selection modes. /// From 93d078309832beda23cc03b39d12708cc8c94b09 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 16:05:55 +1300 Subject: [PATCH 34/38] Updated readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f47350a..9381025 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ Changelog - added `StructVariable` abstract class - improved input handling (see [#4](https://github.com/TomCrypto/AntTweakBar.NET/issues/4)) - added `ListVariable` variable type + - added some more bar properties (still missing a few relating to fonts and icons) 28 November 2014 (v0.4.4) From 8220df9460f57ccd8071e869cbfe3d0f7478a2a4 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 16:16:28 +1300 Subject: [PATCH 35/38] Added font properties. --- Library/Context.cs | 53 ++++++++++++++++++++++++++++++++++++++++++++++ Library/Types.cs | 34 +++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/Library/Context.cs b/Library/Context.cs index 332666c..c28e5a7 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -274,6 +274,59 @@ public void SetOverlap(Boolean overlap) Tw.Define("GLOBAL overlap=" + (overlap ? "true" : "false")); } + /// + /// Sets the font size for all bars. Note the fixed style font is not resizable. + /// + /// The font size to use (default is medium). + public void SetFontSize(BarFontSize fontSize) + { + int index = 0; + + switch (fontSize) { + case BarFontSize.Small: + index = 1; + break; + case BarFontSize.Medium: + index = 2; + break; + case BarFontSize.Large: + index = 3; + break; + } + + Tw.SetCurrentWindow(Identifier); + Tw.Define("GLOBAL fontsize=" + index); + } + + /// + /// Sets the font style for all bars. + /// + /// The font style to use. + public void SetFontStyle(BarFontStyle fontStyle) + { + Tw.SetCurrentWindow(Identifier); + Tw.Define("GLOBAL fontstyle=" + (fontStyle == BarFontStyle.Default ? "default" : "fixed")); + } + + /// + /// Sets whether the bar font can be resized by the user. + /// + /// Whether the font can be resized. + public void SetFontResizable(Boolean resizable) + { + Tw.SetCurrentWindow(Identifier); + Tw.Define("GLOBAL fontresizable=" + (resizable ? "true" : "false")); + } + + /// + /// Sets a font scaling coefficient. Must be called once before initializing the first context. + /// + /// The font scale to use. + public static void SetFontScaling(Double scale) + { + Tw.Define("GLOBAL fontscaling=" + scale); + } + #endregion #region IEnumerable diff --git a/Library/Types.cs b/Library/Types.cs index bb63529..4a81ef6 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -38,6 +38,40 @@ public enum BarButtonAlignment Right, } + /// + /// The possible bar font sizes. + /// + public enum BarFontSize + { + /// + /// Small font. + /// + Small, + /// + /// Nedium font. + /// + Medium, + /// + /// Large font. + /// + Large, + } + + /// + /// The possible bar font styles. + /// + public enum BarFontStyle + { + /// + /// Default (proportional) font. + /// + Default, + /// + /// Fixed (monospace) font. + /// + Fixed, + } + /// /// The possible color selection modes. /// From aa2258ef58e31de479699022b37d05a2805c3e87 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Wed, 17 Dec 2014 16:27:40 +1300 Subject: [PATCH 36/38] Added all missing bar properties, updated changelog. --- Library/Bar.cs | 9 +++++++++ Library/Context.cs | 30 ++++++++++++++++++++++++++++++ Library/Types.cs | 38 ++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/Library/Bar.cs b/Library/Bar.cs index 956bd2e..fe19758 100644 --- a/Library/Bar.cs +++ b/Library/Bar.cs @@ -161,6 +161,15 @@ public BarButtonAlignment ButtonAlignment set { Tw.SetParam(Pointer, null, "buttonalign", value.ToString().ToLower()); } } + /// + /// Gets or sets whether this bar is iconified. + /// + public Boolean Iconified + { + get { return Tw.GetBooleanParam(Pointer, null, "iconified"); } + set { Tw.SetParam(Pointer, null, "iconified", value); } + } + /// /// Gets or sets whether this bar can be iconified by the user. /// diff --git a/Library/Context.cs b/Library/Context.cs index c28e5a7..5bb6c13 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -274,6 +274,36 @@ public void SetOverlap(Boolean overlap) Tw.Define("GLOBAL overlap=" + (overlap ? "true" : "false")); } + /// + /// Sets the icon position for all bars. + /// + /// The icon position (default is bottom left). + public void SetIconPosition(BarIconPosition position) + { + Tw.SetCurrentWindow(Identifier); + Tw.Define("GLOBAL iconpos=" + position.ToString().ToLower()); + } + + /// + /// Sets the icon alignment for all bars. + /// + /// The icon alignment (default is vertical). + public void SetIconAlignment(BarIconAlignment alignment) + { + Tw.SetCurrentWindow(Identifier); + Tw.Define("GLOBAL iconalign=" + alignment.ToString().ToLower()); + } + + /// + /// Sets the icon margin for all bars. + /// + /// The icon margin as an x-y offset in pixels. + public void SetIconMargin(Size margin) + { + Tw.SetCurrentWindow(Identifier); + Tw.Define(String.Format("GLOBAL iconmargin='{0} {1}'", margin.Width, margin.Height)); + } + /// /// Sets the font size for all bars. Note the fixed style font is not resizable. /// diff --git a/Library/Types.cs b/Library/Types.cs index 4a81ef6..e98e2e5 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -72,6 +72,44 @@ public enum BarFontStyle Fixed, } + /// + /// THe possible bar icon positions. + /// + public enum BarIconPosition + { + /// + /// Icons in the bottom left. + /// + BottomLeft, + /// + /// Icons in the bottom right. + /// + BottomRight, + /// + /// Icons in the top left. + /// + TopLeft, + /// + /// Icons in the top right. + /// + TopRight, + } + + /// + /// The possible bar icon alignments. + /// + public enum BarIconAlignment + { + /// + /// Bar icons arranged vertically. + /// + Vertical, + /// + /// Bars icons arranged horizontally. + /// + Horizontal, + } + /// /// The possible color selection modes. /// diff --git a/README.md b/README.md index 9381025..9dc77a6 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Changelog - added `StructVariable` abstract class - improved input handling (see [#4](https://github.com/TomCrypto/AntTweakBar.NET/issues/4)) - added `ListVariable` variable type - - added some more bar properties (still missing a few relating to fonts and icons) + - added all missing bar properties 28 November 2014 (v0.4.4) From 55efab6388df47e089132169c30cde31d8ce8766 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Thu, 18 Dec 2014 15:25:36 +1300 Subject: [PATCH 37/38] Gave StructVariable a default group, and made Group a part of IVariable. --- Library/Group.cs | 4 ++-- Library/StructVariable.cs | 7 +++++-- Library/Variable.cs | 5 +++++ Sample/Program.cs | 16 ++++++++-------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Library/Group.cs b/Library/Group.cs index e3b43b9..9739da6 100644 --- a/Library/Group.cs +++ b/Library/Group.cs @@ -56,7 +56,7 @@ public Group(Bar parentBar) /// The bar the new group should belong to. /// A label to display for the new group. /// Variables to put in the new group. - public Group(Bar parentBar, String label, params Variable[] variables) : this(parentBar) + public Group(Bar parentBar, String label, params IVariable[] variables) : this(parentBar) { if (label == null) { throw new ArgumentNullException("label"); @@ -77,7 +77,7 @@ public Group(Bar parentBar, String label, params Variable[] variables) : this(pa /// The bar the new group should belong to. /// A label to display for the new group. /// Variables to put in the new group. - public Group(Bar parentBar, String label, IEnumerable variables) + public Group(Bar parentBar, String label, IEnumerable variables) : this(parentBar, label, variables.ToArray()) { diff --git a/Library/StructVariable.cs b/Library/StructVariable.cs index 35cdd07..e868bbe 100644 --- a/Library/StructVariable.cs +++ b/Library/StructVariable.cs @@ -67,6 +67,9 @@ public StructVariable(Bar bar, params IValueVariable[] members) OnChanged(e); }; } + + Group = new Group(parentBar); + Group.Label = "unnamed"; } /// @@ -78,7 +81,7 @@ public Group Group set { foreach (var variable in variables) { - (variable as Variable).Group = value; + variable.Group = value; } } } @@ -90,7 +93,7 @@ public void Dispose() Dispose(true); } - private void Dispose(bool disposing) + protected void Dispose(bool disposing) { if (!disposed) { diff --git a/Library/Variable.cs b/Library/Variable.cs index baeede5..8f757a1 100644 --- a/Library/Variable.cs +++ b/Library/Variable.cs @@ -15,6 +15,11 @@ public interface IVariable : IDisposable /// Gets this variable's parent bar. /// Bar ParentBar { get; } + + /// + /// Gets or sets this variable's group. + /// + Group Group { get; set; } } /// diff --git a/Sample/Program.cs b/Sample/Program.cs index fe8b62a..b30ef35 100644 --- a/Sample/Program.cs +++ b/Sample/Program.cs @@ -78,6 +78,12 @@ public Polynomial Value set { polyString.Value = value.ToString(); } } + public Group Group + { + get { return polyString.Group; } + set { polyString.Group = value; } + } + public void Dispose() { polyString.Dispose(); @@ -265,24 +271,18 @@ protected override void OnLoad(EventArgs _) intensityVar.SetDefinition("min=0 max=3 step=0.01 precision=2)"); intensityVar.Label = "Intensity"; - var aCoeffGroup = new Group(configsBar); var aCoeffVar = new ComplexVariable(configsBar, fractal.ACoeff); aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; - aCoeffVar.Group = aCoeffGroup; + aCoeffVar.Group.Label = "Relaxation Coefficient"; aCoeffVar.Step = 0.0002; aCoeffVar.Precision = 4; - aCoeffGroup.Label = "Relaxation Coefficient"; - - var kCoeffGroup = new Group(configsBar); var kcoeff = new ComplexVariable(configsBar, fractal.KCoeff); kcoeff.Changed += delegate { fractal.KCoeff = kcoeff.Value; }; - kcoeff.Group = kCoeffGroup; + kcoeff.Group.Label = "Nova Coefficient"; kcoeff.Step = 0.0002; kcoeff.Precision = 4; - kCoeffGroup.Label = "Nova Coefficient"; - /* Set up a bar for the user to play with the equation */ var fractalBar = new Bar(context, "label='Fractal Equation' valueswidth=400"); From 31667effb655f53fe1bae8bd4a638558621c21b1 Mon Sep 17 00:00:00 2001 From: TomCrypto Date: Sat, 20 Dec 2014 18:16:10 +1300 Subject: [PATCH 38/38] Updated to v0.5.0. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9dc77a6..4813fd3 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Any issues or pull requests are welcome, I especially need help with verifying m Changelog --------- -?? December 2014 (v0.5.0): +20 December 2014 (v0.5.0) - changed `Tw.WindowSize` to accept sizes of (0, 0) to allow AntTweakBar resource cleanup (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3)) - added `ReleaseResources` and `ResetResources` methods to the `Context` class (see [#3](https://github.com/TomCrypto/AntTweakBar.NET/issues/3))