capnmidnight/Primrose

Create a menu control

Closed this issue · 2 comments

Something that makes selecting from a series of choices and drilling down into multiple levels easy for the user to set up.

I may be missing something obvious here, but I'm not sure which menu this would be. Can you give guidance about the best way to start working on this issue? Perhaps describe what choices & levels we'll be selecting. Also, what view is this menu in?

Thanks!

In the browser, we have two different graphics systems, the declarative Document Object Model, where we can write HTML or SVG code that describes the desired end result of our image and have the browser figure out the drawing calls to produce that image, or procedural WebGL, where we have to instruct the system directly on how to paint the objects we care to see.

Making a menu system in DOM is pretty easy. There are standard HTML elements for menus. In the vast majority of cases where they aren't implemented, we frequently make menus out of unordered lists. Here's one built in React with a particularly concise example. Here's one in Ember.

Unfortunately, the DOM and WebGL don't compose together very well. You can make DOM appear on top of WebGL, and if you work at it a little bit you can make WebGL appear on top of DOM, but you can't interleave them in a way that makes sense in the 3D construct of the scene. Hence why Primrose has a from-scratch text editor control in it already.

Also, I hope to one day make Primrose work in systems other than the browser, which almost universally have a JavaScript interpreter with some form of WebGL capability, but not any sort of DOM. Some examples of these systems are AltspaceVR and High-Fidelity.

So, this issue is about making an API for describing menus, rather than creating any particular menu. We need a way to be able to define menus semantically, ala HTML (and cribbing the HTML spec for the structure of such a thing is perfectly acceptable). The API would take that description and add objects to the 3D scene. Which particular objects and where it adds them would in part be influenced by the user's capabilities. Declarative description languages are typically a little better for implementing accessibility features than procedural ones.

Indeed, cribbing the HTML spec for the structure would be a good idea for the future goal of using HTML as a scene description language. A-Frame does this to some extent, but I think they go too far with creating custom elements for no particularly good reason. If Primrose had an HTML-based scene definition that was 1-to-1 with HTML, then we could conceivably take arbitrary HTML documents and re-interpret them for VR.

HTML is declarative, WebGL is procedural. We like the declarativeness of HTML because it's easy to work with, but long-term we can only count of having the proceduralness of WebGL, because it's easier for systems to implement. So we reimplement HTML. Which is not as hard as it sounds, and as long as we're only in the browser, can be cheated for the time being, and isn't necessary straight away. Rather, we implement a fake DOM structure. Which is also not as hard as it sounds, because DOM is just a scene graph, and Three.js is a scene graph, too.

We already have clickable objects. We have the ability to create 3D text. We can also create basic, 2D text. These can be combined with boxes to give a sense of blocks of text and borders. What we need is a system that can describe the structure of the menu, with event handlers, and combine the previous graphics primitives to make a system that reacts to the user.

It'd be okay if this menu at first only generated stacks of boxes in a simple, linear relationship, or required the developer to specify the direction in which to flow the boxes, but eventually the layout of the menu would reflow automatically, depending on the user's system, in similar sense to have we have sites that can reflow their content progressively from desktop systems to smartphones.

I'd imagine that the menu would be defined with code in a way similar to:

var myMenu = new Primrose.Controls.Menu({
    login: {
        visible: true,
        onselect: function() {
            // show the login form
            // on successful login:
            //    - set login menu item to visible=false
            //    - set the logout menu item to visible=true
        }
    },
    logout: {
        visible: false,
        onselect: function() {
            // destroy the user's session
            // on successful logout:
            //    - set logout menu item to visible=false
            //    - set the login menu item to visible=true
        }
    },
    start: {
        onuseraction: function() {
            // some browser APIs like requesting Full Screen access require to be called from a "user action",
            // i.e. a real mouse click or screen tap or key press. Primrose has a "gaze" event handler that is
            // just based on how long you stare at selectable objects. The onuseraction event would
            // define this action as only triggerable by a click/touch/key press, not a gaze.
        }
    },
    options: { // menu items would be nestable
        audio: function(){ // menu items that only specify a function instead of a full object would be assumed to always be visible
            // show the audio options UI
        },
        input: function(){
            // show the user input options UI
        },
        graphics: {
            // arrays such as this would toggle through the list as the user clicked them.
            resolution: ["Low", "Medium", "High"],
            shadowQuality: ["Low", "Medium", "High"]
        },
        cancel: function(){
            // go back to the previous menu level
        }
    }
});

IDK, that's just off the top of my head. That example is almost certainly not feature-complete. This is just an example of a declarative syntax. It'd also have a procedural/object-oriented syntax:

var myMenu = new Primrose.Controls.Menu();

var loginMenuItem = new Primrose.Controls.MenuItem("login");
loginMenuItem.addEventListener("select", function() {
    // show the login form
    // on successful login:
    //    - set login menu item to visible=false
    //    - set the logout menu item to visible=true
});
myMenu.add(loginMenuItem);

var logoutMenuItem = new Primrose.Controls.MenuItem("logout");
logoutMenuItem.visible = false;
logoutMenuItem.addEventListener("select", function() {
    // destroy the user's session
    // on successful logout:
    //    - set logout menu item to visible=false
    //    - set the login menu item to visible=true
});

myMenu.add(logoutMenu);

var startMenuItem = new Primrose.Controls.MenuItem("start");
startMenuItem.addEventListener("useraction", function() {
    // some browser APIs like requesting Full Screen access require to be called from a "user action",
    // i.e. a real mouse click or screen tap or key press. Primrose has a "gaze" event handler that is
    // just based on how long you stare at selectable objects. The onuseraction event would
    // define this action as only triggerable by a click/touch/key press, not a gaze.
});


var optionsMenu = new Primrose.Controls.Menu("options");
myMenu.add(optionsMenu);

var optionsGraphicsMenu = new Primrose.Controls.Menu("graphics");
optionsMenu.add(optionsGraphicsMenu);

var resolutionOptionMenuItem = new Primrose.Controls.MenuToggleList("resolution", ["Low", "Medium", "High"]);
optionsGraphicsMenu.add(resolutionOptionMenuItem);

The first example would be a way to generate code that performs the same sort of operations in the second example.