Minigui's mechanism is ok now, what about policy?

Posted 2023-12-11

I am finally feeling like minigui is reasonably usable, but what can we do to take it to the next level? Random thoughts follow the stats.

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

Random thoughts on minigui

Much of the arsd libs have been more about mechanism, not policy. I give you a bunch of building blocks and tools, but let you choose how you want to put them together yourself. This is flexible and lets you decide which of my libs and which of other libs you want to use, but it can also make things harder to use and document; there's less of straight answers like "just use this and this".

arsd.minigui is mostly a collection of classes right now. You have a bunch of pieces you can use, and they're just about to a point where I think they actually work pretty ok. But there's not much guidance on the way to put it all together. When you make your own widgets, there's a random collection of methods you can override, you can define your own events, but... it is still really a random collection and there's no guarantee it will work nicely with others.

To solve this, I want to start adding more higher level facilities and encouraging people to use them consistently. I can also use these to interoperate with other programs a bit better, hopefully.

Operations on selections

Among the ideas are to expand the DataControllerWidget / dialog function, as well as the facility to build menus from UDAs to take some standard types, or annotated parameters, to deal with selections.

In a standard text widget, the user can expect to select text with their mouse and/or keyboard. This selection can then be cut, copied, deleted, etc., and often can be formatted, like bolded or italicized, by menu options and toolbar buttons. It might even be put in a global selection, such as X's middle click paste system. What if a user wanted to implement a custom widget compatible with those same standard menu options?

A custom widget could, of course, just implement the same things, with a custom menu dispatching the commands to the appropriate place. But let's let the library do that for you. Consider having a global Variant currentSelection, which any widget can set and it triggers behavior. Then your menu is defined like @menu("Edit") void Copy(@currentSelection Variant what).

The param UDA there references the @currentSelection, so the automatic menu wrapper and caller knows that's default argument (or, perhaps you can just use a default argument and reflect over that, but I think the UDA will be easier since it can compare aliases instead of values to identify it). This gives it enough information to understand some of the semantics, so it can disable the Copy menu item when the selection is empty (in such a way it tells the user they need to select something to re-enable it). But you can also call it explicitly, if you want, including with other parameters, as part of the API.

For a function like Copy, specifying that it needs a selection is probably a bit redundant, since that's such a standard operation, but the same pattern can apply to various other user-defined controls too.

Instead of a Variant, it could also be a Widget reference or some other interface widgets can implement, which then provide the selection data on-demand. This would work just like the X clipboard itself - you assert the selection and when an operation wants to work on it, it asks the widget to provide the data in a particular format, through content negotiation. Some menu items could also just post a message to the selection holder widget, commanding it to to the given task on its own internal selection object. This would be similar to how the Mac OS menu works, or indeed, the Windows WM_COMMAND message, just the Mac one is a lil more reflectable.

I think I like the content negotiation, and that can be perhaps made easy by using D's overloading. I'll have to play with it.

Not sure just how I'll do it yet, but I definitely want something that works in a reflectable manner.

Data binding and named params

I wrote this little sample to define a layout in D, using the newer named parameter functionality:

import arsd.minigui;

alias WidgetBuilder = Widget

auto window(T...)(T t) {
        auto window = new Window;
        foreach(child; t) {
                child(window);
        }
        return window;
}

auto horizontalLayout(T...)(T t) {
        return (Widget parent) {
                auto layout = new HorizontalLayout(parent);
                foreach(child; t) {
                        child(layout);
                }
                return layout;
        };
}

auto button(string label, void delegate() onClick = null) {
        return (Widget parent) {
                auto btn = new Button(label, parent);
                if(onClick)
                btn.addEventListener((ClickEvent ce) {
                        onClick();
                });
                return btn;
        };
}

void main() {
        auto w = window(
                horizontalLayout(
                        button(label: "Click Me", onClick: delegate () { messageBox("hello!"); }),
                        button(label: "Or Me", onClick: delegate () { messageBox("bye."); }),
                ),

                button(label: "And me")
        );
        w.loop();
}

minigui already has a data controller widget, which can do two-way binding between widgets and application binding, and can break structs down to their components with some degree of user-defined layout. What it doesn't do, however, is let you change the widgets themselves based on data state.

Now, I'm not convinced that's actually a good idea: changing widgets themselves vs just changing some properties on widgets are two very different things. ReactJS is just plain bad - it is a fundamentally bad idea and them trying to make it suck less has led to growing complication and since only some state is actually represented in its model, you can easily get bugs out of it too. I don't want to clone it, but what if we just a few of the ideas?

D has __FILE__ and __LINE__ which you can pass as default arguments, even to templates, to help identify a particular function call. We could, maybe, cache the control itself and just put in changed properties to it. These segments could be triggered by change trackers, a bit of opDispatch stuff, the same as I already have in the data controller widget, to forward data at runtime.

Or, we could make the value functions declare their data dependencies statically, as function parameters. Again, using a bit of reflection, it knows what needs what and can wire up change events that way. This can't say for sure if something needs updating based on a dynamic condition, but it'd also make the state requirements more explicit for testing purposes.

I haven't thought so much about this, but I think the named params feature being new to D might open up new possibilities to explore here too.