james_lohr's picture

Keyboard stack

Hello,

I'm creating a game, and one of the issues I've encountered is with managing which handlers have been added to the keyboard events. For example, suppose you're in a menu and you've added a handler so that you can move up/down within the menu. Now suppose you select some control within the menu (e.g. a box which allows free text to be entered) which has its own handlers . Whilst the control is active, it is desirable that no other handlers are listening to keyboard events. What is the standard way for dealing with this?

In the end I went for a KeyboardStack, which has the following usage

var keyboardStack = new keyboardStack(Keyboard);
 
keyboardStack.AddKeyDown( ControlA.KeyDown);
keyboardStack.AddKeyDown( ControlB.KeyDown);
 
//here, controlA, controlB will receive input
 
keyboardStack.Push();
keyboardStack.AddKeyDown( ControlC.KeyDown);
 
//here, only control C will receive input
 
keyboardStack.Push();
keyboardStack.AddKeyDown( ControlD.KeyDown);
 
//here, only control D will receive input
 
keyboardStack.Pop();
 
//here, only control C again
 
keyboardStack.Pop();
 
//here, only controlA, controlB again
 
keyboardStack.Pop();
 
//nothing will receive input

This has certainly tidied up my code a lot, but I was just wondering if there a standard/better way of doing it.

Thanks,

James L.

If anyone wants to use my KeyboardStack class, then it's here:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK.Input;
 
namespace JOpenTKUtils
{
    public class KeyboardStack 
    {
        private KeyboardDevice keyboardDevice;
        private Stack<HandlerLists> handlerStack;
 
        private class HandlerLists
        {
            public List<EventHandler<KeyboardKeyEventArgs>> KeyDownList = new List<EventHandler<KeyboardKeyEventArgs>>();
            public List<EventHandler<KeyboardKeyEventArgs>> KeyUpList = new List<EventHandler<KeyboardKeyEventArgs>>();
        }
 
 
        public void AddKeyDown(EventHandler<KeyboardKeyEventArgs> keyDown)
        {
            if (handlerStack.Count == 0)
                Push();
 
            handlerStack.Peek().KeyDownList.Add(keyDown);
            keyboardDevice.KeyDown += keyDown;
        }
 
 
        public void AddKeyUp(EventHandler<KeyboardKeyEventArgs> keyUp)
        {
            if (handlerStack.Count == 0)
                Push();
 
            handlerStack.Peek().KeyUpList.Add(keyUp);
            keyboardDevice.KeyUp += keyUp;
        }
 
        public void RemoveKeyDown(EventHandler<KeyboardKeyEventArgs> keyDown)
        {
            if (handlerStack.Count == 0)
                return;
 
            handlerStack.Peek().KeyDownList.Remove(keyDown);
            keyboardDevice.KeyDown -= keyDown;
        }
 
 
        public void RemoveKeyUp(EventHandler<KeyboardKeyEventArgs> keyUp)
        {
            if (handlerStack.Count == 0)
                return;
 
            handlerStack.Peek().KeyUpList.Remove(keyUp);
            keyboardDevice.KeyUp -= keyUp;
        }
 
 
 
 
        private void RemoveAllHandlers(HandlerLists lists)
        {
            foreach (var handler in lists.KeyDownList)
                keyboardDevice.KeyDown -= handler;
 
            foreach (var handler in lists.KeyUpList)
                keyboardDevice.KeyUp -= handler;
        }
 
        private void AddAllHandlers(HandlerLists lists)
        {
            foreach (var handler in lists.KeyDownList)
                keyboardDevice.KeyDown += handler;
 
            foreach (var handler in lists.KeyUpList)
                keyboardDevice.KeyUp += handler;
        }
 
 
        public void Push()
        {
 
            if(handlerStack.Count != 0)
                RemoveAllHandlers(handlerStack.Peek());
 
            var handlerLists = new HandlerLists();
            handlerStack.Push(handlerLists);
 
        }
 
        public void Pop()
        {
            if (handlerStack.Count == 0)
                throw new Exception("Attempted to pop an empty KeyboardStack.");
 
            RemoveAllHandlers(handlerStack.Pop());
 
            if (handlerStack.Count != 0)
                AddAllHandlers(handlerStack.Peek());
        }
 
 
        public KeyboardStack(KeyboardDevice keyboard)
        {
 
            handlerStack = new Stack<HandlerLists>();
            this.keyboardDevice = keyboard;
        }
 
 
    }
}

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

This is one of the issues with event-based input code (OpenTK trunk now offers an additional, polling-based API that does not suffer from this - components can now poll for input independently, on a need-by-need basis).

There are many possible solutions to this issue. Your approach is flexible and should work well. Operating systems use a similar approach, combining the concept of 'input focus' with a stack (the window at the top of the stack has input focus; close that and the focus moves to the next window in the stack).

james_lohr's picture

Thanks for the information Fiddler.

In the past I've always used polling-based APIs, and for this reason my original solution was to wrap the OpenTK event API with my own API I could then poll. The issue with this was that it introduced additional latency, and in fact it was less clean than using the event API directly.

Now that I've changed my code to use the events via my InputStack class (I've modified it to include mouse input now too), it feels much cleaner than even a polling-based API, so I think I shall stick to events for the time being.

Is the plan to support polling and events alongside each other indefinitely (both implemented to minimize latency), or is there the risk that one will become obsolete or have an additional latency attached?

FXDD's picture

For me, its not only you that encounters that certain problem. Keyboard stack really exist. FXDD