Login Menu with user inputs

Where you do inputText += you need to test if either the shift keys are down using IsKeyDown and Keys.LeftShift and Keys.RightShift and append the upper case version of the key. For the digits you will have to have a switch because there is no mapping but for letters you could try the ToString method.

I am not using the framework keyboard and key classes so should I implement IsKeyDown in mine or wouldn´t be worth and should I just use the static Keyboard instead?

 public class _Keyboard
    {

        public KeyboardState newKeyboard, oldKeyboard;       //newKeyboard tracks the actual frame while oldKeyboard tracks the previous frame

        public List<_Key> pressedKeys = new List<_Key>(), previousPressedKeys = new List<_Key>();

        public _Keyboard()
        {

        }

        public virtual void Update()
        {
            newKeyboard = Keyboard.GetState();

            GetPressedKeys();

        }

        public void UpdateOld()
        {
            oldKeyboard = newKeyboard;

            previousPressedKeys = new List<_Key>();
            for (int i = 0; i < pressedKeys.Count; i++)
            {
                previousPressedKeys.Add(pressedKeys[i]);
            }
        }


        public bool GetPress(string KEY)
        {

            for (int i = 0; i < pressedKeys.Count; i++)
            {

                if (pressedKeys[i].key == KEY)
                {
                    return true;
                }

            }


            return false;
        }

        public virtual void GetPressedKeys()
        {
            pressedKeys.Clear();
            for (int i = 0; i < newKeyboard.GetPressedKeys().Length; i++)
            {

                pressedKeys.Add(new _Key(newKeyboard.GetPressedKeys()[i].ToString(), 1));

            }
        }

        public List<_Key> ReturnKeysPressed()
        {
            return pressedKeys;
        }

        public virtual bool GetSinglePress(string KEY)
        {
            for (int i = 0; i < pressedKeys.Count; i++)
            {
                bool isIn = false;

                for(int j = 0; j < previousPressedKeys.Count; j++)
                {
                    if(pressedKeys[i].key == previousPressedKeys[j].key)
                    {
                        isIn = true;
                        break;
                    }
                }

                if(!isIn && (pressedKeys[i].key == KEY || pressedKeys[i].print == KEY))
                {
                    return true;
                }
            }

            return false;
        }

    }

 public class _Key
    {
        public int state;
        public string key, print, display;

        public _Key(string _key, int _state)
        {
            key = _key;                             //name of the key
            state = _state;                         //starts as 1, when key is held down it´s value is 2
            MakePrint(key);
        }

        public virtual void Update()
        {
            state = 2;

        }

        public void MakePrint(string KEY)
        {
            display = KEY;

            string tempStr = "";

            if (KEY == "A" || KEY == "B" || KEY == "C" || KEY == "D" || KEY == "E" || KEY == "F" || KEY == "G" || KEY == "H" || KEY == "I" || KEY == "J" || KEY == "K" || KEY == "L" || KEY == "M" || KEY == "N" || KEY == "O" || KEY == "P" || KEY == "Q" || KEY == "R" || KEY == "S" || KEY == "T" || KEY == "U" || KEY == "V" || KEY == "W" || KEY == "X" || KEY == "Y" || KEY == "Z")
            {
                tempStr = KEY.ToLower();
            }
            if (KEY == "Space")
            {
                tempStr = " ";
            }
            if (KEY == "OemCloseBrackets")
            {
                tempStr = "]";
                display = tempStr;
            }
            if (KEY == "OemOpenBrackets")
            {
                tempStr = "[";
                display = tempStr;
            }
            if (KEY == "OemMinus")
            {
                tempStr = "-";
                display = tempStr;
            }
            if (KEY == "OemPeriod" || KEY == "Decimal")
            {
                tempStr = ".";
            }
            if (KEY == "D1" || KEY == "D2" || KEY == "D3" || KEY == "D4" || KEY == "D5" || KEY == "D6" || KEY == "D7" || KEY == "D8" || KEY == "D9" || KEY == "D0")
            {
                tempStr = KEY.Substring(1);
            }
            else if (KEY == "NumPad1" || KEY == "NumPad2" || KEY == "NumPad3" || KEY == "NumPad4" || KEY == "NumPad5" || KEY == "NumPad6" || KEY == "NumPad7" || KEY == "NumPad8" || KEY == "NumPad9" || KEY == "NumPad0")
            {
                tempStr = KEY.Substring(6);
            }


            print = tempStr;
        }
    }

Also is the key name Left Shift and Right Shift ? What about the Backspace?

Based on your code you could try something like this:

        if (Keyboard.GetState().IsKeyDown(Keys.LeftShift) || Keyboard.GetState().IsKeyDown(Keys.RightShift))
        {
            if (KEY == "A" || KEY == "B" || KEY == "C" || KEY == "D" || KEY == "E" || KEY == "F" || KEY == "G" || KEY == "H" || KEY == "I" || KEY == "J" || KEY == "K" || KEY == "L" || KEY == "M" || KEY == "N" || KEY == "O" || KEY == "P" || KEY == "Q" || KEY == "R" || KEY == "S" || KEY == "T" || KEY == "U" || KEY == "V" || KEY == "W" || KEY == "X" || KEY == "Y" || KEY == "Z")
            {
                tempStr = KEY;
            }
        }
        else
        {
            if (KEY == "A" || KEY == "B" || KEY == "C" || KEY == "D" || KEY == "E" || KEY == "F" || KEY == "G" || KEY == "H" || KEY == "I" || KEY == "J" || KEY == "K" || KEY == "L" || KEY == "M" || KEY == "N" || KEY == "O" || KEY == "P" || KEY == "Q" || KEY == "R" || KEY == "S" || KEY == "T" || KEY == "U" || KEY == "V" || KEY == "W" || KEY == "X" || KEY == "Y" || KEY == "Z")
            {
                tempStr = KEY.ToLower();
            }
        }

You will need to handle the digits differently because there is no mapping between digits and special characters. It will be the same idea though. For backspace you just do a substring of the string with -1 length.

What I was asking is if the name of the backspace key is backspace ,sorry if my question wasn´t clear :sweat_smile:

It is Keys.Back so I guess Back would be the string name for it.

Thanks :grin:

For the special characters do I have to put each of the keys in the first if condition?
Example:

 if (Keyboard.GetState().IsKeyDown(Keys.LeftShift) || Keyboard.GetState().IsKeyDown(Keys.RightShift))
            {
                if (KEY == "A" || KEY == "B" || KEY == "C" || KEY == "D" || KEY == "E" || KEY == "F" || KEY == "G" || KEY == "H" || KEY == "I" || KEY == "J" || KEY == "K" || KEY == "L" || KEY == "M" || KEY == "N" || KEY == "O" || KEY == "P" || KEY == "Q" || KEY == "R" || KEY == "S" || KEY == "T" || KEY == "U" || KEY == "V" || KEY == "W" || KEY == "X" || KEY == "Y" || KEY == "Z")
                {
                    tempStr = KEY;
                }

                if(KEY == "D1" )
                {
                    tempStr = "!";
                }
                else if (KEY == "D3")
                {
                    tempStr = "#";
                }
            }

If you’re interested you can try out my UI library. As it turns out, I’ve used a login screen as a test UI to make a lot of it. If you let me know where something doesn’t make sense it would help me out a lot to make it easier to use.

I’m definitely going to need to make a video tutorial, the house is pretty noisy though.

If not, making your own UI is a lot of fun! I’ve been doing it for 3 years now. :slight_smile: Mine is based off behaviors, so each box has a list of interchangeable behaviors. One of them allows you to edit text.

Here is the code I use to edit text. It uses this behavior as well to draw the text. Feel free to take and use any of it.

using DownUnder.UI.Widgets.DataTypes;
using DownUnder.Utilities;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended;
using System;
using System.Collections.Generic;
using System.Text;

namespace DownUnder.UI.Widgets.Behaviors.Visual.DrawTextBehaviors
{
    public class DrawEditableText : WidgetBehavior, ISubWidgetBehavior<DrawText>
    {
        public DrawText BaseBehavior => Parent.Behaviors.GetFirst<DrawText>();

        public override string[] BehaviorIDs { get; protected set; } = new string[] { DownUnderBehaviorIDs.FUNCTION };

        private bool Active => active_backing;

        readonly float caret_blink_time;
        StringBuilder edit_text = new StringBuilder();
        int caret_position = 0;
        int highlight_start = 0;
        int highlight_end = 0;
        /// <summary> If this is less than half _CaretBlinkTime then the caret won't be drawn. </summary>
        float caret_blink_timer = 0f;
        bool active_backing = false;
        List<RectangleF> highlight_area;
        List<RectangleF> text_area;
        bool allow_draw = false;
        bool clicking = false;
        bool editing_enabled_backing = true;

        public DrawEditableTextSettings Settings { get; set; } = new DrawEditableTextSettings();

        public bool EditingEnabled
        {
            get => editing_enabled_backing;
            set
            {
                if (!value && Active)
                {
                    editing_enabled_backing = value;
                    active_backing = value;
                    DeactivateEditing();
                    return;
                }
                editing_enabled_backing = value;
            }
        }

        public void ActivateEditing()
        {
            if (active_backing) return;
            edit_text.Clear();
            edit_text.Append(BaseBehavior.Text);
            if (Settings.HighlightTextOnActivation) HighlightRange(0, edit_text.Length);
            else MoveCaretTo(Parent.ParentDWindow.WindowFont.IndexFromPoint(edit_text.ToString(), Parent.CursorPosition, true));
            BaseBehavior.EnableDefaultDraw = false;
            active_backing = true;
        }

        public void DeactivateEditing(bool keep_changes = false)
        {
            if (!active_backing) return;
            if (keep_changes) BaseBehavior.Text = edit_text.ToString();
            //BaseBehavior.TextEntryRules.ApplyFinalCheck(edit_text); ??
            edit_text.Clear();
            BaseBehavior.EnableDefaultDraw = true;
            active_backing = false;
            caret_blink_timer = 0f;
        }

        /// <summary> The start of the highlighted text (if active). </summary>
        int _HighlightPosition => (highlight_start > highlight_end) ? highlight_end : highlight_start;

        /// <summary> The length of the highlighted text (if active). </summary>
        int _HighlightLength => (highlight_start > highlight_end) ? highlight_start - highlight_end : highlight_end - highlight_start;

        bool _CaretCurrentlyDrawn => caret_blink_timer / caret_blink_time < 0.5f;

        int _CaretLine
        {
            get
            {
                if (caret_position == 0) return 0;
                string source = edit_text.ToString().Substring(0, caret_position);
                int count = 0;
                foreach (char c in source)
                    if (c == '\n') count++;
                return count;
            }
        }

        /// <summary> The number of lines in this text. </summary>
        public int NumOfLines
        {
            get
            {
                string source = edit_text.ToString();
                int count = 0;
                foreach (char c in source)
                    if (c == '\n') count++;
                return count;
            }
        }

        int _BeginningOfCurrentLine {
            get {
                for (int i = caret_position - 1; i >= 0; i--)
                    if (edit_text[i] == '\n') return i + 1;
                return 0;
            }
        }

        int _EndOfCurrentLine {
            get {
                for (int i = caret_position; i < edit_text.Length; i++)
                    if (edit_text[i] == '\n') return i;
                return edit_text.Length;
            }
        }

        /// <summary> Returns the start and end index of the word the caret is over. Includes the following space. </summary>
        public Tuple<int, int> CurrentWord {
            get {
                int start = 0;

                for (int i = caret_position - 1; i >= 0; i--) {
                    if (edit_text[i] == '\n' || edit_text[i] == ' ') {
                        start = i + 1;
                        break;
                    }
                }

                for (int i = caret_position; i < edit_text.Length; i++) {
                    if (edit_text[i] == '\n' || edit_text[i] == ' ') {
                        if (edit_text[i] == ' ') i++;
                        return new Tuple<int, int>(start, i);
                    }
                }

                return new Tuple<int, int>(start, edit_text.Length);
            }
        }

        public DrawEditableText() : base() {
            caret_blink_time = OSInterface.CaretBlinkTime;
        }

        protected override void Initialize()
        {
        }

        protected override void ConnectEvents()
        {
            Parent.OnUpdate += Update;
            Parent.OnDraw += Draw;
            Parent.OnClick += ClickAction;
            Parent.OnDoubleClick += DoubleClickAction;
            Parent.OnTripleClick += TripleClickAction;
            Parent.OnConfirm += ConfirmAction;
            Parent.OnSelectOff += FocusOffAction;
        }

        protected override void DisconnectEvents()
        {
            Parent.OnUpdate -= Update;
            Parent.OnDraw -= Draw;
            Parent.OnClick -= ClickAction;
            Parent.OnDoubleClick -= DoubleClickAction;
            Parent.OnTripleClick -= TripleClickAction;
            Parent.OnConfirm -= ConfirmAction;
            Parent.OnSelectOff -= FocusOffAction;
        }

        public override object Clone()
        {
            throw new NotImplementedException();
        }

        public void Update(object sender, EventArgs args)
        {
            if (!Parent.UpdateData.UIInputState.PrimaryClick) clicking = false;
            if (!Settings.RequireDoubleClick && Parent.IsPrimaryHovered) Parent.ParentDWindow.UICursor = MouseCursor.IBeam;
            if (!Active) return;
            Vector2 offset = Parent.PositionInWindow.ToVector2().Floored();
            UIInputState inp = Parent.UpdateData.UIInputState;

            if (clicking) MoveCaretTo(Parent.ParentDWindow.WindowFont.IndexFromPoint(edit_text.ToString(), Parent.CursorPosition, true));
            
            // Movement of the caret
            if (inp.TextCursorMovement.Left && caret_position != 0) MoveCaretTo(caret_position - 1);
            if (inp.TextCursorMovement.Right && caret_position != edit_text.Length) MoveCaretTo(caret_position + 1);
            if (inp.TextCursorMovement.Up)
            {
                if (_CaretLine == 0) MoveCaretTo(0);
                else
                {
                    MoveCaretTo(
                        Parent.ParentDWindow.WindowFont.IndexFromPoint
                        (
                            edit_text.ToString(),
                            Parent.ParentDWindow.WindowFont.GetCharacterPosition(edit_text.ToString(), caret_position) - new Vector2(0f, Parent.ParentDWindow.WindowFont.MeasureString("|").Y),
                            true
                        )
                    );
                }
            }

            if (inp.TextCursorMovement.Down)
            {
                if (_CaretLine == NumOfLines) MoveCaretTo(edit_text.Length);
                else
                {
                    MoveCaretTo(
                        Parent.ParentDWindow.WindowFont.IndexFromPoint
                        (
                            edit_text.ToString(),
                            Parent.ParentDWindow.WindowFont.GetCharacterPosition(edit_text.ToString(), caret_position) + new Vector2(0f, Parent.ParentDWindow.WindowFont.MeasureString("|").Y),
                            true
                        )
                    );
                }
            }

            if (inp.Home) MoveCaretTo(_BeginningOfCurrentLine);
            if (inp.End) MoveCaretTo(_EndOfCurrentLine);

            // Editing the text
            bool text_changed = false;

            if (inp.BackSpace)
            {
                if (_HighlightLength != 0)
                {
                    text_changed |= DeleteHighlightedText();
                }
                else if (edit_text.Length > 0 && caret_position != 0)
                {
                    edit_text.Remove(caret_position - 1, 1);
                    MoveCaretTo(caret_position - 1);
                    text_changed = true;
                }
            }

            if (inp.Delete)
            {
                if (_HighlightLength != 0)
                {
                    text_changed |= DeleteHighlightedText();
                }
                else if (edit_text.Length > 0 && caret_position != edit_text.Length)
                {
                    edit_text.Remove(caret_position, 1);
                    MoveCaretTo(caret_position);
                    text_changed = true;
                }
            }

            if (inp.SelectAll) HighlightRange(0, edit_text.Length);
            
            if (inp.Copy || inp.Cut)
            {
                if (_HighlightLength > 0)
                {
                    char[] t = new char[_HighlightLength];
                    edit_text.CopyTo(_HighlightPosition, t, 0, _HighlightLength);
                    OSInterface.CopyTextToClipBoard(new string(t));
                }
                if (inp.Cut) text_changed |= DeleteHighlightedText();
            }

            if (inp.Paste) text_changed |= InsertText(OSInterface.GetTextFromClipboard(), caret_position, true);
            
            // Insert typed text
            text_changed |= InsertText(Parent.UpdateData.UIInputState.Text, caret_position, true);

            if (text_changed)
            {
                if (Settings.LiveUpdate) BaseBehavior.Text = edit_text.ToString();
            }

            text_area = Parent.ParentDWindow.WindowFont.MeasureStringAreas(edit_text.ToString());
            highlight_area = Parent.ParentDWindow.WindowFont.MeasureSubStringAreas(edit_text.ToString(), _HighlightPosition, _HighlightLength, true);
            caret_blink_timer += Parent.UpdateData.ElapsedSeconds;

            bool over_highlighted_text = false;
            foreach (RectangleF text in highlight_area) {
                text.Offset(offset);
                if (text.Contains(Parent.CursorPosition)) over_highlighted_text = true;
            }

            if (Parent.IsPrimaryHovered && (!over_highlighted_text || clicking)) Parent.ParentDWindow.UICursor = MouseCursor.IBeam;
            if (caret_blink_timer >= caret_blink_time) caret_blink_timer -= caret_blink_time;

            allow_draw = true;
        }

        private void MoveCaretTo(int index, bool no_highlight = false)
        {
            if (caret_position != index) caret_blink_timer = 0f;
            caret_position = index;
            if (!Parent.UpdateData.UIInputState.Shift && !clicking || no_highlight) highlight_start = caret_position;
            highlight_end = caret_position;
        }

        private void HighlightRange(int start, int end)
        {
            caret_position = end;
            highlight_start = start;
            highlight_end = end;
            caret_blink_timer = 0f;
        }

        private bool InsertText(string text, int index, bool no_highlight = false)
        {
            bool text_changed = false;
            if (text == "") return text_changed;
            if (DeleteHighlightedText())
            {
                index = caret_position;
                text_changed = true;
            }
            int added_chars = Settings.TextEntryRules.CheckAndInsert(edit_text, text, index);
            if (added_chars != 0)
            {
                MoveCaretTo(index + added_chars, no_highlight);
                text_changed = true;
            }
            return text_changed;
        }

        private bool DeleteHighlightedText()
        {
            int highlight_length = _HighlightLength;
            if (highlight_length == 0) return false;
            int highlight_position = _HighlightPosition;
            edit_text.Remove(highlight_position, highlight_length);
            highlight_start = highlight_position;
            highlight_end = highlight_position;
            caret_position = highlight_position;
            return true;
        }

        /// <summary> Is called when the parent is clicked. </summary>
        private void ClickAction(object sender, EventArgs args)
        {
            if (Active)
            {
                MoveCaretTo(Parent.ParentDWindow.WindowFont.IndexFromPoint(edit_text.ToString(), Parent.CursorPosition, true));
                clicking = true;
            }

            if (!Settings.RequireDoubleClick) ActivateEditing();
        }

        /// <summary> Is called when the parent is double clicked. </summary>
        private void DoubleClickAction(object sender, EventArgs args)
        {
            if (!EditingEnabled) return;

            if (Active)
            {
                clicking = false;
                Tuple<int, int> word = CurrentWord;
                //Console.WriteLine(word);
                HighlightRange(word.Item1, word.Item2);
                return;
            }

            ActivateEditing();
        }

        private void ConfirmAction(object sender, EventArgs args)
        {
            DeactivateEditing(true);
        }

        private void FocusOffAction(object sender, EventArgs args)
        {
            DeactivateEditing(false);
        }

        /// <summary> Is called when the parent is triple clicked. </summary>
        private void TripleClickAction(object sender, EventArgs args)
        {
            if (Active)
            {
                clicking = false;
                HighlightRange(_BeginningOfCurrentLine, _EndOfCurrentLine);
                return;
            }
        }

        public void Draw(object sender, WidgetDrawArgs args)
        {
            if (!Active) return;
            if (!allow_draw) return;
            Vector2 offset = args.DrawingArea.Position.WithOffset(BaseBehavior.TextPosition).Floored();

            if (_HighlightLength > 0)
            {
                foreach (var rect in highlight_area)
                {
                    rect.Offset(offset);
                    Parent.SpriteBatch.FillRectangle(rect, Color.LightBlue);
                }
            }

            BaseBehavior.ForceDrawText(args.DrawingArea.Position, edit_text.ToString());

            if (_CaretCurrentlyDrawn)
            {
                Vector2 position = Parent.ParentDWindow.WindowFont.GetCharacterPosition(edit_text.ToString(), caret_position) + offset + new Vector2(1, 0);
                Vector2 position2 = position + new Vector2(0, 20);
                Parent.SpriteBatch.DrawLine(position, position2, Parent.VisualSettings.TextColor, 1);
            }
        }
    }
}

My project is a university project so I think that using an already made UI would be less valuable than making my own

University, eh? I never went to school, probably why I still work as a deli clerk.

Are you still stuck on the repeating text part? I made a BufferedKeyboard class that basically keeps a list of bools that independently keep track of timing, where each bool represents one key. If you want to go down the easier route, you should definitely look into using MonoGame’s Window.TextInput EventHandler. If you get too crazy parsing input it might get a little too ambitious for a school project.

This event handler will handle timing for you, so no repeating values. The downside is that certain keys are not kept track of correctly, should you just add chars to the end of your string, and you will need to account for them manually.

Yes. You need a separate if for each of the digits you want to use. Same for any other special character that has a shift state associated with it.

I would opt against using Keyboard.GetState(), unless it’s being wrapped by another class, at all since it’s not meant to be used for text input. It’s also bad practice to try to account for every key manually.

General rule of thumb is when you have a line like this

if (KEY == “A” || KEY == “B” || KEY == “C” || KEY == “D” || KEY == “E” || KEY == “F” || KEY == “G” || KEY == “H” || KEY == “I” || KEY == “J” || KEY == “K” || KEY == “L” || KEY == “M” || KEY == “N” || KEY == “O” || KEY == “P” || KEY == “Q” || KEY == “R” || KEY == “S” || KEY == “T” || KEY == “U” || KEY == “V” || KEY == “W” || KEY == “X” || KEY == “Y” || KEY == “Z”)

There’s going to be an easier way to do it. Your professor isn’t going to like that.

It could be simplified to if (KEY >= “A” && KEY <= “Z”)

But then he still has to account for the numbers and special characters, and shift and caps lock, and repeating character timing. Window.TextInput will handle all of that for you by actually interfacing with your OS and your typing settings and keyboard layouts.

Doing it with KeyboardState would be fine… if you had a very specific reason not to use the built in event handler.

You mean ASCII table? This is my only class left and I never understood that table :sweat_smile:

Strings are made up of chars. If you don’t know, a char is identical to a byte (or a number from 0-255). So a way you can look at it is that your string is made up of bytes. Every byte represents one letter.

if ('g' == 103) Console.WriteLine("g is the same as 103");

It’s not important to memorize this table, but it’s good to have it around in fringe cases.

Note that all strings use ASCII, although it is outdated. ASCII is limited to 256 characters, utf-8 can have 2,097,152 different characters. That’s why you can use many more different kinds of characters on websites than you can in your C# program. :slight_smile:

I tried to make that simplification bug I am getting this error

That is because you are using strings instead of chars. You can’t compare strings with > < because they aren’t numerical values.

I’m a bit surprised that the exception was thrown, but it’s a good thing that it was.

In some languages you CAN compare strings using < and >, but you REALLY don’t want to be doing that unless you know what will happen and explicitly WANT that behavior. The order of numbers, letters, and symbols basically follows ASCII value tables, and that’s almost never what is desired. After all, is “A” really less than “a” and greater than “1”?

Evaluating equality with strings is safe. Evaluating > and < with strings seems likely to always have undesirable edge cases.

If you wanted to do that you could convert a single character string to a char like so

char.Parse(KEY) >= 'A'
//OR 
KEY.ToCharArray()[0] >= 'A'

Notice the single quotes around the A because it’s a char not a string.