FEZ Touch UI Controls Class

Peddy has posted a very cool FEZ Touch UI Controls class to the code section.

[url]http://code.tinyclr.com/project/328/fez-touch-ui-controls/[/url]

This is very easy to use ( we used it in one of my classes yesterday).

Something different. Looks good, thanks!

The adjustment proposed in http://www.tinyclr.com/forum/2/3392/
it is possible to show the LED circular.

Before I have FEZTouch.UIControls.cs drawing of the LED changes to:


        protected override void Draw()
        {
            if (IsOn)
            {
                touchscreen.Circle(X_Pos, Y_Pos, Radius, OnColor); 
                touchscreen.FillCircle(X_Pos + 4, Y_Pos + 4, Radius - 4, OnColor); 
            }
            else
            {
                touchscreen.Circle(X_Pos, Y_Pos, Radius, OnColor); 
                touchscreen.FillCircle(X_Pos + 1, Y_Pos + 1, Radius - 1, OffColor); 
            }
        }

There is a note that says it requires a ‘FillCIrcle’ function in the FezTouch driver but where is this code? Don’t see it anywhere on that page or anywhere else. Make thigs a bit confusing to try and use it when part of what you need is absent.

The driver code for the FEZ Touch provided by GHI does not provide a FillCircle() method. I created one myself, but it was so ugly, I didn’t bother posting it here.

So, if you want to use circular LED controls on the FEZ Touch, you’ll need to implement a FillCircle() method, or replace it with FillRectangle() (which is available in the driver code provided by GHI).

You can change the Draw() method for the LED class to this to fix the problem:

        protected override void Draw()
        {
            if (IsOn)
                touchscreen.FillRectangle(X_Pos, Y_Pos, (Radius*2), (Radius*2), OnColor);
            else
                touchscreen.FillRectangle(X_Pos, Y_Pos, (Radius*2), (Radius*2), OffColor);
        }

I will make this change in the code on code.tinyclr.com.

That would make it more clear to everyone. I just commented those lines out for now. I am enjoying using the library.

I have been playing with things and made a few hacks in the slider class so that the whole region is not erased and redrawn every time. The old handle location is erased and the line redrawn. This prevents the whole area from having that flashing look.

It might be that some performance improvements can be made by changing the way the touch event handler is done in each control. The simplest way would be to define a rectangle that is the area covered by the control. You then just need to make a few simple checks to see if the touch point is within the rectangle or not. This lets you quickly skip past the controls that don’t need any updating done.

The use of properties can also be a big performance hit in NETMF. Having external access through a property for things that don’t change a lot is not to bad. But anytime you have to access the same property numerous times during in an event handler you pay a big time penalty.

Yeah, I’m sure there’s room for improvement when it comes to optimizing. I didn’t spend a lot of time going into the nitty gritty of how I could squeeze every bit of performance out of things. I do agree that the “flickering” is distracting and annoying.

The TextArea control is also something I was hoping could be improved at some point.

I’ll take a look at what you’ve suggested, and make some changes.

Thanks for the input.

Paul

I’ve made the changes to the code as suggested. The Slider control looks much better when redrawing now. I also removed many of the class properties where they weren’t necessary.

After I updated the code on code.tinyclr.com, it said that my updates removed one point from my experience. Not that I’m super concerned about how many points I have (and especially if it’s only losing one point), but do we get penalized for making updates to the our code?

Real programmers don’t make mistakes :slight_smile:

I think it is based on content (word count?). You can get extra points after update or you can loose some. Not a big deal.

Yeah… not a big deal. I was just curious. Haven’t really paid attention to how points are computed. I’m just happy that my avatar isn’t a picture of a little girl with a lollipop anymore. :dance:

If you optimized your code, you probably reduced the size. One of the metrics for calculating points is how long the code is. On the other hand, you get more points by adding comments and documentation.

I have made a small change to the code, which allows you to “disable” a control when required.
Its so simple I’m not including the actual code change, just how to do it, so hopefully it will get picked
up in the next release.
Create a boolean per control called something like isEnabled, with a default value of true.
add a method Enable, and a method Disable, which just toggle this variable.
In the routine that checks if the touch is “inside” the control, always return false if the control
is disabled.

Oh yes and a small bug.

The Momentary button does not have any code that implements the “Draw” method, so it fails if you try
to do something like change the background colour of the button, then “Draw” to update the button.

@ Ross2,
Thanks for the input. I’ll take a look sometime this week when I have some free time. Hopefully sooner, rather than later. :slight_smile:

I had a bit more time to play with the slider control. The first thing I did was to add a simple, but effective ‘IsPointInControl’.

 protected virtual Boolean IsPointInControl(int x, int y)
        {
            const int extra = 5;
            if (x >= X_Pos - extra && x <= X_Pos + Width + extra
                && y >= Y_Pos - extra && y <= Y_Pos + Height + extra)
            {
                return true;
            }

            return false;
        }

The slider class was changed around to make it mostly orientation agnostic. This greatly simplified things: There is still room for improvement but its a good start: Some more thought should be put into the access to public variables MinValue and MaxValue.

using System;
using Microsoft.SPOT;
using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.FEZ;
using System.Threading;

namespace FEZTouch.UIControls
{
    /// <summary>
    /// Slider UI Control for FEZTouch
    /// We assume the control is horizontal until we get redy to paint it
    /// </summary>
    public class Slider : Control
    {
        public enum eOrientation
        {
            Horizontal,
            Vertical
        }

        const int HandleSize = 20;
        const int HalfHandle = HandleSize / 2; // avoid calculating this over and over again

        int SliderRange;    // Range in pixels the slider can move (line lenght)
        int CurrentPos;     // Current position of slider within SliderRange
        int LastPos;        // Last position of slider within SliderRange
        int Offset;         // Offset to start of start of ValueRange
        int ValueRange;     // Range in values (int) that the slider can represent
        int CurrentValue;   // Current value of control

        int Length;         // Length in pixels of control
        public int MinValue;// Value of left hand side of control
        public int MaxValue;// Value of right hand side of control
        eOrientation Orientation;                  // Horizontalaly or Verticaly oriented
        FEZ_Components.FEZTouch.Color LineColor;   // Color of line
        FEZ_Components.FEZTouch.Color HandleColor; // Color of slider handle

        /// <summary>
        /// Let's protect changing the current value of the control externally
        /// </summary>
        public int Value
        {
            get { return CurrentValue; }

            set
            {
                if (value > MinValue && value < MaxValue)
                {
                    LastPos = CurrentPos;
                    CurrentValue = value;
                    CurrentPos = Value2Position(value);
                }
                else
                {
                    throw new ArgumentException("Slider value out of range");
                }
                Draw();
                DoValueChanged(value);
            }
        }

        #region Constructors & Initialization

        public Slider(int x, int y, FEZ_Components.FEZTouch ft, int length, int min, int max, eOrientation orient)
        {
            Init(x, y, ft, length, min, max, orient);
        }


        /// <summary>
        /// Initializes the Slide Control
        /// </summary>
        /// <param name="x">X position of control</param>
        /// <param name="y">Y position of control</param>
        /// <param name="ft">reference to FEZTouch</param>
        /// <param name="length">Lenght, in pixels, of control</param>
        /// <param name="min">Minimum value of control</param>
        /// <param name="max">Maximum value of control</param>
        /// <param name="orient">Orintation of control</param>
        void Init(int x, int y, FEZ_Components.FEZTouch ft, int length, int min, int max, eOrientation orient)
        {
            X_Pos = x;
            Y_Pos = y;
            Width = (orient == eOrientation.Horizontal) ? length : HandleSize;
            Height = (orient == eOrientation.Horizontal) ? HandleSize : length;

            Length = length;
            SliderRange = Length - HandleSize;
            Offset = X_Pos;
            MinValue = min;
            MaxValue = max;
            ValueRange = MaxValue - MinValue;
            touchscreen = ft;
            Orientation = orient;
            CurrentValue = MinValue;
            CurrentPos = Value2Position(MinValue);
            LastPos = CurrentPos;
            LineColor = FEZ_Components.FEZTouch.Color.White;
            HandleColor = FEZ_Components.FEZTouch.Color.Red;
            BGColor = FEZ_Components.FEZTouch.Color.Black;

            Draw();
            touchscreen.TouchMoveEvent += new FEZ_Components.FEZTouch.TouchEventHandler(touchscreen_TouchMoveEvent);
        }

        #endregion

        #region Events

        public delegate void ValueChangedDelegate(int val);
        public event ValueChangedDelegate ValueChanged;

        internal void DoValueChanged(int val)
        {
            if (ValueChanged != null) ValueChanged(val);
        }

        /// <summary>
        /// Touch handler, first find out if this control should react
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        void touchscreen_TouchMoveEvent(int x, int y)
        {
            if (IsPointInControl(x, y))
            {
                int tempPosition;
                if (Orientation == eOrientation.Horizontal) 
                {
                    tempPosition = ClipValue(x, X_Pos, X_Pos + SliderRange);
                    tempPosition -= X_Pos;
                }
                else
                {
                    tempPosition = ClipValue(y, Y_Pos, Y_Pos + SliderRange);
                    tempPosition -= Y_Pos;
                }

                LastPos = CurrentPos;
                CurrentPos = tempPosition;
                CurrentValue = Position2Value(CurrentPos);
                Debug.Print("Val:" + CurrentValue.ToString() + " pos:" + CurrentPos.ToString());
                Draw();
            }
        }

        #endregion

        #region Rendering

        /// <summary>
        /// Draws control onto FEZTouch screen
        /// </summary>
        protected override void Draw()
        {
            if (Orientation == eOrientation.Horizontal)
            {
                touchscreen.FillRectangle(X_Pos + LastPos, Y_Pos, HandleSize, HandleSize, BGColor);         // Clear only last handle
                touchscreen.FillRectangle(X_Pos, Y_Pos + HalfHandle, Length, 1, LineColor);                 // Draw line for handle
                touchscreen.FillRectangle(X_Pos + CurrentPos, Y_Pos, HandleSize, HandleSize, HandleColor);  // Draw handle at new position
            }
            else
            {
                touchscreen.FillRectangle(X_Pos, Y_Pos + LastPos, HandleSize, HandleSize, BGColor);        // Clear only last handle
                touchscreen.FillRectangle(X_Pos + HalfHandle, Y_Pos, 1, Length, LineColor);                             // Draw line for handle
                touchscreen.FillRectangle(X_Pos, Y_Pos + CurrentPos, HandleSize, HandleSize, HandleColor); // Draw handle at new position
            }
        }

        #endregion

        #region Helpers

        /// <summary>
        /// Calculated position based on value
        /// </summary>
        /// <param name="value"></param>
        /// <returns>position of slider</returns>
        private int Value2Position(int value)
        {
            double ValuePercent = (ClipValue(value, MinValue, MaxValue) - MinValue) / (double)ValueRange; //% of range
            return (int)(ValuePercent * SliderRange);
        }

        /// <summary>
        /// Calculate value of control based on position
        /// </summary>
        /// <param name="position">Position of control on screen</param>
        /// <returns>Value of control</returns>
        /// todo: should not need to subtract offset here?
        private int Position2Value(int position)
        {
            double PositionPercent = position / (double)SliderRange;
            int val = (int)(PositionPercent * ValueRange) + MinValue;
            return val;
        }

        // clip input value to range we care about
        // should do this in IsPointInControl
        private int ClipValue(int val, int min, int max)
        {
            return System.Math.Max(min, System.Math.Min(val, max));
        }


        #endregion

    }
}

I’m finding some time to clean up some of the issues that have been mentioned in this thread.

One thing I wanted to mention… I had to remove most of the comment lines in my code when I posted it, due to the size of the file. I don’t recall exactly, but there was a 1500 line limit or something.

I’d like to be able to include those comments, to help clarify the code, and obviously, for automatic documentation purposes.

Does my code need to be broken up into multiple files? What’s the best solution here? Any thoughts?

@ peddy: Thanks for your ui-controls
I tried to compile it, but got a lot of errors. Could you add a c#-project to your zip file? Would be great!

Andreas, you must be doing something wrong. I think this is the 2nd post I’ve seen you ask for the same, so I think we have some other issue we should be helping you work out. I’d suggest you start a new thread though, since it’s not just one piece of code that you’re having trouble with, and it’s not really relevant to this code