diff --git a/Library/Bar.cs b/Library/Bar.cs index 489fb7d..fe19758 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. @@ -71,39 +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. - /// - /// 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. /// @@ -140,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. /// @@ -158,6 +134,42 @@ 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 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. /// @@ -203,6 +215,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. /// @@ -223,14 +253,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); } @@ -247,7 +277,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/Context.cs b/Library/Context.cs index faee4b8..5bb6c13 100644 --- a/Library/Context.cs +++ b/Library/Context.cs @@ -139,25 +139,88 @@ public bool HandleMouseClick(Tw.MouseAction action, Tw.MouseButton button) return Tw.MouseClick(action, button); } + /* 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 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) { + 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); + } + } else { + ignoreKeyPress = false; + } + + return false; } /// - /// Notifies this context of a special key press. + /// Notifies this context that a key has been pressed. /// /// The key pressed. - /// The key modifiers pressed. - public bool HandleKeyPress(Tw.SpecialKey key, Tw.KeyModifier modifiers) + /// The key modifiers. + public bool HandleKeyDown(Tw.Key key, Tw.KeyModifiers modifiers) { Tw.SetCurrentWindow(Identifier); - return Tw.KeyPressed((int)key, modifiers); + lastModifiers = modifiers; + + if (SpecialKeys.Contains(key)) { + ignoreKeyPress = true; // special keys + return Tw.KeyPressed(key, modifiers); + } + + 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 false; + } + + /// + /// Notifies this context that a key has been released. + /// + /// The key released. + /// The key modifiers. + public bool HandleKeyUp(Tw.Key key, Tw.KeyModifiers modifiers) + { + lastModifiers = Tw.KeyModifiers.None; + ignoreKeyPress = false; + return false; } /// @@ -201,6 +264,99 @@ 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")); + } + + /// + /// 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. + /// + /// 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/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 7a02548..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. @@ -60,24 +60,29 @@ 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. /// 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), + it.type = Tw.DefineEnum(Guid.NewGuid().ToString(), GetEnumLabels()), Variable.SetCallbacks[id], Variable.GetCallbacks[id], IntPtr.Zero, null); @@ -93,7 +98,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 +154,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/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/Group.cs b/Library/Group.cs new file mode 100644 index 0000000..9739da6 --- /dev/null +++ b/Library/Group.cs @@ -0,0 +1,170 @@ +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; + } + + /// + /// 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 IVariable[] variables) : this(parentBar) + { + if (label == null) { + throw new ArgumentNullException("label"); + } else if (variables == null) { + throw new ArgumentNullException("variables"); + } + + 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/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/Library.csproj b/Library/Library.csproj index f157ea3..fa17c0a 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 @@ -49,6 +47,8 @@ + + @@ -69,6 +69,8 @@ + + \ No newline at end of file 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); + } + } +} 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 ca99ae4..a8938f1 100644 --- a/Library/Native.cs +++ b/Library/Native.cs @@ -62,7 +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.KeyModifiers modifiers); [DllImport(DLLName, EntryPoint = "TwEventSFML")] [return: MarshalAs(UnmanagedType.Bool)] @@ -86,6 +92,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 +285,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 +315,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; + } + } } /// @@ -394,12 +450,45 @@ 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) + { + return NativeMethods.TwKeyPressed((int)key, modifiers); + } + + /// + /// Call this function to inform AntTweakBar when a keyboard event occurs. + /// + /// 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(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. + /// 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); + } + + /// + /// This function checks if a key event would be processed but without processing it. This could be helpful to prevent bad handling report. + /// + /// 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) { - return NativeMethods.TwKeyPressed(key, modifiers); + return KeyTest((char)key, modifiers); } /// @@ -441,6 +530,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 +1134,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,15 +1184,90 @@ 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."); } 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; + + if (kv.Key == null) { + throw new ArgumentNullException("", "Member name cannot be null."); + } + + if (kv.Value.Def == null) { + throw new ArgumentNullException("", "Member definition string cannot be null."); + } + + 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. /// @@ -1090,6 +1303,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 +1319,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/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/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 cfe149a..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 { @@ -24,7 +21,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. @@ -140,9 +137,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/StructVariable.cs b/Library/StructVariable.cs new file mode 100644 index 0000000..e868bbe --- /dev/null +++ b/Library/StructVariable.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +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 + { + /// + /// Occurs when the user changes this variable's value. + /// + public event EventHandler Changed; + + /// + /// Raises the Changed event. + /// + public void OnChanged(EventArgs e) + { + ThrowIfDisposed(); + + if (Changed != null) { + Changed(this, e); + } + } + + /// + /// Gets this variable's parent bar. + /// + public Bar ParentBar { get { return parentBar; } } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly Bar parentBar; + + /// + /// Gets or sets the value of this variable. + /// + public abstract T Value { get; set; } + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + protected IList variables; + + /// + /// Creates a new struct variable in a given bar. + /// + /// The bar to create the struct variable in. + /// The set of member variables. + public StructVariable(Bar bar, params IValueVariable[] members) + { + if (bar == null) { + throw new ArgumentNullException("bar"); + } else if (members == null) { + throw new ArgumentNullException("members"); + } else if (members.Length == 0) { + throw new ArgumentException("At least one variable."); + } else if (members.Any((var) => (var.ParentBar != bar))) { + throw new ArgumentException("All variables must be in the same bar."); + } + + variables = new List(members); + parentBar = bar; + + foreach (var variable in variables) { + variable.Changed += (s, e) => { + OnChanged(e); + }; + } + + Group = new Group(parentBar); + Group.Label = "unnamed"; + } + + /// + /// Gets or sets the group this variable represents. + /// + public Group Group + { + get { return (variables.First() as Variable).Group; } + set + { + foreach (var variable in variables) { + variable.Group = value; + } + } + } + + #region IDisposable + + public void Dispose() + { + Dispose(true); + } + + protected void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) { + foreach (var variable in variables) { + variable.Dispose(); + } + } + + disposed = true; + } + } + + private bool disposed = false; + + /// + /// Throws an ObjectDisposedException if this variable has been disposed. + /// + protected void ThrowIfDisposed() + { + if (disposed) { + throw new ObjectDisposedException(GetType().FullName); + } + } + + #endregion + + public override String ToString() + { + return String.Format("[StructVariable<{0}>: Group={1}, Value={2}]", typeof(T).Name, Group, Value); + } + } +} diff --git a/Library/Types.cs b/Library/Types.cs index 500a153..e98e2e5 100644 --- a/Library/Types.cs +++ b/Library/Types.cs @@ -1,25 +1,132 @@ using System; +using System.Diagnostics; using System.Runtime.InteropServices; namespace AntTweakBar { /// - /// Specifies the possible color selection modes. + /// 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 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 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. /// 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 { @@ -57,25 +164,40 @@ 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. + /// The graphics API's AntTweakBar supports. /// public enum GraphicsAPI { @@ -106,7 +228,7 @@ public enum GraphicsAPI } /// - /// Specifies the valid parameter value types to SetParam. + /// The valid parameter value types to SetParam. /// public enum ParamValueType { @@ -129,12 +251,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. + /// The different possible variable type, excluding enums and structs. /// public enum VariableType { @@ -229,7 +351,64 @@ public enum VariableType }; /// - /// Specifies the possible mouse actions recognized by AntTweakBar. + /// 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() + { + 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); + } + } + + /// + /// The possible mouse actions recognized by AntTweakBar. /// public enum MouseAction { @@ -244,57 +423,57 @@ 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 KeyModifier + 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 SpecialKey + public enum Key { None = 0, Backspace = '\b', @@ -329,6 +508,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/Library/Variable.cs b/Library/Variable.cs index ed2389b..8f757a1 100644 --- a/Library/Variable.cs +++ b/Library/Variable.cs @@ -6,10 +6,42 @@ namespace AntTweakBar { + /// + /// Implemented by all AntTweakBar variables. + /// + public interface IVariable : IDisposable + { + /// + /// Gets this variable's parent bar. + /// + Bar ParentBar { get; } + + /// + /// Gets or sets this variable's group. + /// + Group Group { get; set; } + } + + /// + /// 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. @@ -87,10 +119,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", ""); + } + } } /// @@ -153,7 +205,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/README.md b/README.md index d4fd555..4813fd3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -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. @@ -13,9 +13,9 @@ 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 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,40 +23,50 @@ 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 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`). - 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. +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 ------------------- @@ -66,69 +76,26 @@ 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: - - 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). - - - **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: - - 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. - - - **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! - - - **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. - 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 - * 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 - * add extensions (in a separate assembly) to help interoperate with popular frameworks? (maybe) - Changelog --------- -Next release: +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)) - 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 + - added `StructVariable` abstract class + - improved input handling (see [#4](https://github.com/TomCrypto/AntTweakBar.NET/issues/4)) + - added `ListVariable` variable type + - added all missing bar properties 28 November 2014 (v0.4.4) diff --git a/Sample/Fractal.cs b/Sample/Fractal.cs index a696170..7f1ad67 100644 --- a/Sample/Fractal.cs +++ b/Sample/Fractal.cs @@ -59,7 +59,22 @@ 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 + { + threshold = value; + SetupShaders(); + } } private bool hardcodePolynomial; @@ -157,7 +172,6 @@ private void SetShaderVariables() ACoeff = aCoeff; KCoeff = kCoeff; - Iterations = iterations; Palette = palette; if (!hardcodePolynomial) @@ -225,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); @@ -277,6 +291,7 @@ private void SetupOptions() hardcodePolynomial = true; intensity = 1; + threshold = 3; palette = Color4.Red; shading = ShadingType.Standard; 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 ed56676..b30ef35 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 + sealed 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,132 +40,102 @@ class PolynomialVariable } } - /// - /// The actual backing variable. - /// - public StringVariable PolyString { get; set; } + public Bar ParentBar { get { return polyString.ParentBar; } } - public PolynomialVariable(Bar bar, String poly, String def = null) + public event EventHandler Changed { - PolyString = new StringVariable(bar, poly, def); - PolyString.Validating += (s, e) => { e.Valid = (Polynomial.Parse(e.Value, symbols) != null); }; + add { polyString.Changed += value; } + remove { polyString.Changed -= value; } } - public Polynomial Polynomial + public void OnChanged(EventArgs e) { - get { return Polynomial.Parse(PolyString.Value, symbols); } + polyString.OnChanged(e); } - } - - /// - /// Not a real variable (just contains two variables for the - /// real/imaginary parts and puts them in a single group). - /// - class ComplexVariable : IDisposable - { - private readonly DoubleVariable re, im; /// - /// Occurs when the user changes the variable. + /// The actual backing variable. /// - public event EventHandler Changed; + private StringVariable polyString { get; set; } - /// - /// Raises the Changed event. - /// - public void OnChanged(EventArgs e) + public PolynomialVariable(Bar bar, Polynomial poly, String def = null) { - if (Changed != null) - Changed(this, e); + polyString = new StringVariable(bar, poly.ToString(), def); + polyString.Validating += (s, e) => { + e.Valid = (Polynomial.Parse(e.Value, symbols) != null); + }; } - /// - /// Gets or sets the value of this variable. - /// - public Complex Value + public String Label { - 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); - } + get { return polyString.Label; } + set { polyString.Label = value; } } - public ComplexVariable(Bar bar, Complex initialValue) + public Polynomial Value { - re = new DoubleVariable(bar, initialValue.Real); - im = new DoubleVariable(bar, initialValue.Imaginary); + get { return Polynomial.Parse(polyString.Value, symbols); } + set { polyString.Value = value.ToString(); } + } - re.Changed += (sender, e) => OnChanged(EventArgs.Empty); - im.Changed += (sender, e) => OnChanged(EventArgs.Empty); + public Group Group + { + get { return polyString.Group; } + set { polyString.Group = value; } + } - re.Label = "Real"; - im.Label = "Imaginary"; + public void Dispose() + { + polyString.Dispose(); + } + } + + sealed class ComplexVariable : StructVariable + { + private DoubleVariable Re { get { return (variables[0] as DoubleVariable); } } + private DoubleVariable Im { get { return (variables[1] as DoubleVariable); } } - Label = "undef"; + public ComplexVariable(Bar bar, Complex initialValue) + : base(bar, new DoubleVariable(bar, initialValue.Real), + new DoubleVariable(bar, initialValue.Imaginary)) + { + Re.Label = "Real"; + Im.Label = "Imaginary"; } - public Double Step + public override Complex Value { - get { return re.Step; } - set + get { - re.Step = value; - im.Step = value; + return new Complex(Re.Value, Im.Value); } - } - public Double Precision - { - get { return re.Precision; } set { - re.Precision = value; - im.Precision = value; + Re.Value = value.Real; + Im.Value = value.Imaginary; } } - public String Label + public Double Step { - get { return re.Group; } + get { return Re.Step; } set { - re.Group = value; - im.Group = value; + Re.Step = value; + Im.Step = value; } } - #region IDisposable - - ~ComplexVariable() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) + public Double Precision { - if (disposed) - return; - - re.Dispose(); - im.Dispose(); - - disposed = true; + get { return Re.Precision; } + set + { + Re.Precision = value; + Im.Precision = value; + } } - - private bool disposed = false; - - #endregion } class Program : GameWindow @@ -205,75 +177,38 @@ 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) { 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 HandleKeyInput(Context context, KeyboardKeyEventArgs e, bool down) { - 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; - - 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 }, - }; + modifiers |= Tw.Mappings.OpenTK.Modifiers[(int)KeyModifiers.Control]; - if (mapping.ContainsKey(e.Key)) - return context.HandleKeyPress(mapping[e.Key], modifier); - else + if (Tw.Mappings.OpenTK.Keys.ContainsKey((int)e.Key)) { + 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; + } } #endregion @@ -293,38 +228,17 @@ 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) => 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); 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,18 +268,18 @@ 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); aCoeffVar.Changed += delegate { fractal.ACoeff = aCoeffVar.Value; }; - aCoeffVar.Label = "Relaxation Coeff."; + aCoeffVar.Group.Label = "Relaxation Coefficient"; 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.Group.Label = "Nova Coefficient"; kcoeff.Step = 0.0002; kcoeff.Precision = 4; @@ -376,30 +290,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 @@ -408,18 +322,20 @@ protected override void OnLoad(EventArgs _) * keeping a reference to them. That way we don't need to track them at all. */ + var symbolicVarGroup = new Group(fractalBar); + for (char symbol = 'A'; symbol <= 'F'; ++symbol) { var symbolVar = new DoubleVariable(fractalBar); - symbolVar.Group = "Symbolic Variables"; symbolVar.Label = symbol.ToString(); + symbolVar.Group = symbolicVarGroup; symbolVar.Step = 0.0002f; symbolVar.Precision = 4; 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 @@ -427,7 +343,8 @@ protected override void OnLoad(EventArgs _) } /* Start the symbol list closed to avoid clutter */ - fractalBar.OpenGroup("Symbolic Variables", false); + symbolicVarGroup.Label = "Symbolic Variables"; + symbolicVarGroup.Open = false; } private enum FractalPreset @@ -445,10 +362,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 (HandleKeyInput(context, e, true)) { + return; + } + + if (e.Key == Key.Escape) { + Close(); + } + } + + protected override void OnKeyUp(KeyboardKeyEventArgs e) + { + base.OnKeyUp(e); + + if (HandleKeyInput(context, e, false)) { + 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) diff --git a/Sample/Shader.cs b/Sample/Shader.cs index e12d2f1..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,26 +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(); 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)"); + 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 < 1e-8) break;"); + str.AppendLine(" if (l <= " + Math.Pow(10, -threshold) + ") break;"); str.AppendLine(" }"); str.AppendLine(); str.AppendLine(" return vec4(z, speed, float(t));"); @@ -198,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(); @@ -207,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 * r.z / iters);"); + str.AppendLine(" return colorize(z, r.xy, pow(r.z, intensity), r.w * intensity / " + iterations + ");"); str.AppendLine("}"); return str.ToString(); 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()]