libglui / glui

GLUI is a GLUT-based C++ user interface library which provides controls such as buttons, checkboxes, radio buttons, and spinners to OpenGL applications. It is window-system independent, using GLUT or FreeGLUT.
Other
194 stars 82 forks source link

Node sorting, group-IDs, and drag-drop customization philosophy #101

Open m-7761 opened 5 years ago

m-7761 commented 5 years ago

Yesterday I developed a simple function glui_sort_node that can actually be quite powerful for users.

LINKAGE void glui_sort_node(Node &node, bool(*pred)(Node*,Node*), bool recurse);

I wrote it for sorting the List controls' items. But it can just as well be used to completely rearrange a UI after it is created. The constructors are inflexible, you can only pass to them the parent, and the children are placed on the back. The arguments are already a bit unwieldy.

What I imagine a rather complex app would do with this, is build the UI, and then design a predicate for the sort function, that will do further (more algorithmic) operations on the UI afterward, before it's presented for the first time.

That could involve the app having serialized the UI to remember how users left it. The predicate can also initialize more complex members. It's a little inefficient because it will see nodes multiple times, but since it would do double-duty it could check-off nodes that are already initialized. The idea is, users cannot move controls into different containers, but can reorder them within their logical stacks.

A big motivation I have for doing custom UI is I want to standardize a keyboard navigation system I've developed for Win32. It's built-in system is imperfect and inflexible. But basically Win32 has a concept of "groups" within which you can use arrows to navigate instead of tab. Tab jumps between groups.

In my work I've basically made a version of this on steroids, that works across all controls. So every control class can interact correctly with groups. I want to replicate this if only so I can eventually port this software.

I'm definitely going to be implementing this, and so it would be a good candidate for back-porting to GLUI, which I would like to use as a miniature, lightweight embodiment of the UI philosophy I'm trying to develop. I'm developing a Blender killer.

Most of this would not involve GLUI's code, but I think that the group-ID ought to be a basic member. Win32 doesn't have groups IDs. It has an is-group/is-not-group flag. The ID as I imagine it is not a fixed value like a user-id, but it is a first-order sorting term. Of course, its up to the predicate write to use it. But more importantly it will inform keyboard navigation. Instead of a group flag, GLUI would ask: is the group ID identical? If so, then this is a group.

I think if GLUI were to facilitate actually customizing the UI, it would need to have a consistent drag-drop policy, that it would just use to inform the user code of drag-drop activity. Just like dragging the window border has to actually be implemented by the user code, GLUI would not actually reorder anything, but just notify of drag-drop, and render the interaction...

My idea for drag-drop (UI customization) is the labels (name) would act like drag handles, and the controls can be dragged within their groups to be moved within the group, which must be contiguous in the node's sibling ordering. But if they are dragged outside of their group, then that is communicating that the entire group needs to be dragged, and so the focus shifts from the one control to all of the group.

When the user code completes the drag, it reassigns the group IDs, since they probably double for z-order IDs in most implementations; or at least they are so implicitly if the user implements this by setting the group IDs and then calling glui_sort_node to rearrange the linked-list.

(Edited: I'm switching to std::stable_sort to not reorder intragroup controls. Drag-dropping a single element onto a sibling is straightforward.) Anyway, I'm describing a paradigm. In my more heavyweight project I think also there will be a pop-up menu system for hiding controls. It would present an overview of all possible controls, and checkboxes to hide/show them. I'm really striving for something that is automatically intuitive.

nigels-com commented 5 years ago

This is a good idea, in my view.

m-7761 commented 5 years ago

I'm looking at Example #5. It has an awkward bottom subwindow that requires inserting new GLUI_Column() after every element.

I'm planning on making the column functionality a bool value so dynamic_cast is less necessary.

To drag-drop elements around they need to remember if they have arbitrary columns or other formatting features attached. One simple serialization pattern is to use negative numbers to indicate a column is attached, and sort by absolute-value.

Below is a scheme that is maybe GLUI/GLUT centric. It uses temporary objects to format the UI. They remember their settings, so that there is less repetition involved. And they can pass values to a callback that lets them be inlined as constants instead of sourced from the control.

        GLUI_Library::Stack st = glui1;
        GLUI_Library::Shelf sh = glui2;     
        GLUI_Rotation *rot =
        sh.add("Objects",rot)->set_spin(1);     
        sh.set_identity(1);
        sh.set_grouping();
        sh.set_variable(view_rotate);
        sh^[=] //or sh.set_callback();
        { 
           //callback implementation here
        };
        sh.sep(); //add visual separator

A Stack is vertical. A Shelf is used here because these are horizontal. The only difference is the shelf inserts the column setting. This avoids reading into subwindows orientation.

I've been meaning to review wxWidget's system, to see if it gives me ideas. I've looked at it before, enough to determine it would require a wrapper.

In this system set_grouping and sep can take values, but they have default behavior, like adding +1 to the current group number. (EDITED: It should automatically add +1 to the user-id so it doesn't have to be set for contiguous identifiers.)

The assignment/constructor becomes the container node. The rot value doesn't have to be assigned to, but these days all compilers squeal if you don't initialize it. Compiler writers must be the most inconsiderate people on earth. It illustrates set_spin is modified to return itself for chaining.

set_identity is the ID. optional. set_variable is a live-variable. You kind of have to know what it wants. I think the constructors are a little top-heavy, and not very extensible. Especially for "styles". The lambda callback uses an overloaded operator ^ because I don't like C++ lambda style. It's so alien to my C++ eyes.

Something like push and pop can be nice for nested containers. I think Stack and Shelf would be able to assign to one another, being nearly identical.