GUI planning: composition vs inheritance

Started by
21 comments, last by SyncViews 5 years ago

@SyncViews

On 3/28/2019 at 1:13 PM, SyncViews said:

Where are you drawing the line as "client codebase"? For example ones with their own markup style (e.g. HTML/JS especially with things like angular, XAML, Android XML, etc.) strive to move as much as possible into their declarative syntax and out of the imperative code/language so that the code mostly just handles events, data binding, and bits of dynamic creation/destruction of views, so you don't have a bunch of "new Button(50, 100, 'OK'))" type code.

Yeah that sounds about like what I am going for. I don't want the client to have a lot of freedom over the GUI. Things like click to focus, hot items, etc should be built in/non-configurable. This is mostly just for my game projects so I stop remaking the same code over and over.

Quote

I basically solved this by re-ordering the child references in a "root" UI node so that my child lists are always bottom to top and a tree-iteration can be naturally bottom to top or top to bottom. This doesn't allow me a CSS style "z-index" property however, if I want to add that I would need to track the z-index items separately, and having a separate render/z-order "collection" of all UI elements would certainly work

I stole from your post and copied your idea of re-ordering the child references. I created a root UI node which is entity # 0. It is invisible, has a global position of 0,0. and is the only node with no parent. It's children nodes are considered "windows". When I click on a window it gets shuffled to the front of Entity#0's children and gets rendered first. It's great!

Quote

Was there any GUI programming you are approaching this from?

I am trying to recreate Windows GUI. I don't have any experience. I just feel that Windows style is a good starting point because it is familiar and it is flexible and I've been using it since the 90's.

Quote

This doesn't allow me a CSS style "z-index" property however, if I want to add that I would need to track the z-index items separately, and having a separate render/z-order "collection" of all UI elements would certainly work............

........You also need to consider the need for mouse events to determine the top-most window at any given location, and possibly keyboard navigation to determine the tab-order.

So my problem right now has to do with this:

I don't know how to select only the highest Z level item for the Hot Item. It's easy for the windows(children of entity#0) because they are all in one vector of children, but the highest Z level item is in the tree and I don't know what to do.

There are four important chunks of data:

Hot Item: Whatever the mouse is hovering over, at the highest Z level, is the hot item.

Active Item: Whatever is the hot item when the L-mouse button is clicked on becomes the active item, until the mouse is released. If the hot item is the same as it was when the L-mouse was clicked then activate that item or else set Active Item to Null.

Focused Item: Focused Item (correct me if I am wrong) is like a text input box when it is ready to receive input.

Focused Window: Like in Windows, entities you can alt-tab through which when focused on will be sent to top of render order. These are all children of entity#0.

There are only one of each of these data so I have them all as global members of my singleton (evil I know) AppWindow class.

 

I just can't figure out how to selectively only make the highest Z level entity the Hot Item.

Thanks so far for the replies they've been very helpful.

 

Edit:

I am thinking I might want to make a Z-level map. I can add a "z height" member to the node component and entities can keep track of how high they are. Or they can add themselves to a static vector of vectors of entities. I can traverse the vectors top down checking mouse collision. That is the only thing I can think of right now.

 

Advertisement

 

On 4/2/2019 at 2:49 AM, maltman said:
On 3/28/2019 at 8:13 PM, SyncViews said:

Where are you drawing the line as "client codebase"? For example ones with their own markup style (e.g. HTML/JS especially with things like angular, XAML, Android XML, etc.) strive to move as much as possible into their declarative syntax and out of the imperative code/language so that the code mostly just handles events, data binding, and bits of dynamic creation/destruction of views, so you don't have a bunch of "new Button(50, 100, 'OK'))" type code.

Yeah that sounds about like what I am going for. I don't want the client to have a lot of freedom over the GUI. Things like click to focus, hot items, etc should be built in/non-configurable. This is mostly just for my game projects so I stop remaking the same code over and over.

Yes, definitely keep things like buttons, text controls, window headers, etc. in the library code. But what I meant is there is still a gap from just doing that (as Win32 does, QT, wxWidgets, Swing, etc.) and using a declarative syntax.

e.g. A list of buttons (e.g. a game menu, although pretty uncommon in other desktop applications) in wxWidgets, might look a bit like this:


MyPanel::MyPanel(wxWindow *parent)
    : wxPanel(parent)
{
    auto newGame     = new wxButton(this, wxID_ANY, "New Game");
    auto loadGame    = new wxButton(this, wxID_ANY, "Load Game");
    auto multiplayer = new wxButton(this, wxID_ANY, "Multiplayer");
    auto settings    = new wxButton(this, wxID_ANY, "Settings");
    auto exit        = new wxButton(this, wxID_ANY, "Exit");

    wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
    SetSizer(sizer);
    sizer->Add(newGame,     1, wxALL, 10);
    sizer->Add(loadGame,    1, wxALL, 10);
    sizer->Add(multiplayer, 1, wxALL, 10);
    sizer->Add(settings,    1, wxALL, 10);
    sizer->Add(exit,        1, wxALL, 10);

    newGame->Bind(wxEVT_BUTTON, &MyPanel::onNewGame, this);
    loadGame->Bind(wxEVT_BUTTON, &MyPanel::onLoadGame, this);
    multiplayer->Bind(wxEVT_BUTTON, &MyPanel::onMultiplayer, this);
    settings->Bind(wxEVT_BUTTON, &MyPanel::onSettings, this);
    exit->Bind(wxEVT_BUTTON, &MyPanel::onExit, this);
}

In Android, you might have this:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:orientation="vertical">
    <Button
        android:onClick="onNewGame"
        android:layout_width="100dp"
        android:layout_gravity="center"
        android:text="@string/new_game" />
    <Button
        android:onClick="onLoadGame"
        android:layout_width="100dp"
        android:layout_gravity="center"
        android:text="@string/load_game" />
    <Button
        android:onClick="onMultiplayer"
        android:layout_width="100dp"
        android:layout_gravity="center"
        android:text="@string/multiplayer" />
    <Button
        android:onClick="onSettings"
        android:layout_width="100dp"
        android:layout_gravity="center"
        android:text="@string/settings" />
    <Button
        android:onClick="onExit"
        android:layout_width="100dp"
        android:layout_gravity="center"
        android:text="@string/exit" />
</LinearLayout>

public class MainActivity extends Activity 
{
    public void onCreate(Bundle savedInstanceState) 
    {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
    }

    public void onNewGame(View v) { ... }
    public void onLoadGame(View v) { ... }
    public void onMultiplayer(View v) { ... }
    public void onSettings(View v) { ... }
    public void onExit(View v) { ... }
}

Which internally basically reads the XML then does the same as the wxWidgets code. In fact all the objects are readily available (e.g. the android.widget.Button class), useful for more dynamic things or creating new custom widgets/controls. Sometimes with things like Android, a graphic WYSIWYG "what you see is what you get" tool is used, although for a game that step is likely overkill, and is a major project in its own right.

 

On 4/2/2019 at 2:49 AM, maltman said:
Quote

Was there any GUI programming you are approaching this from?

I am trying to recreate Windows GUI. I don't have any experience. I just feel that Windows style is a good starting point because it is familiar and it is flexible and I've been using it since the 90's.

Well final style, maybe not the best place to go for API inspiration. I tend to use wxWidgets or at least MFC as wrappers when I want a "native" Windows application in C++. Essentially it is an OOP model with inheritance in C (see RegisterClass and cbClsExtra, cbWndExtra, Get/Set WindowLongPtr, chaining WNDPROC, etc.), The WNDPROC is basically your "on_x" event handler and events go through subclasses, parent windows, etc.

Then for the extra window tree hierarchy, CreateWindow(Ex)(A|W) take a HWND hWndParent and then there is EnumChildWindows to get children, GetParent, FindWindowEx, ChildWindowFromPoint, etc.so that bit basically works like in my example with each window having a list of children.

e.g.a WM_LBUTTONDOWN starts on the top-most/child window at the cursor position, if that doesn't handle it, the event then goes to it's parent, and so on until the root/top-level window. A WM_KEYDOWN starts on the window with keyboard focus.

On 4/2/2019 at 2:49 AM, maltman said:

So my problem right now has to do with this:

I don't know how to select only the highest Z level item for the Hot Item. It's easy for the windows(children of entity#0) because they are all in one vector of children, but the highest Z level item is in the tree and I don't know what to do.

I believe Win32 does it the same way but a little more advanced, although you might want to check (HTML/CSS is really the only example I can think of for being special, possibly in a way that adds unneeded complexity but you can say HTML is a documents structural tree, and not its visual tree, essentially meaning you need two ordered trees).

Because every window is either top-level (root/popup/floating/other-terms) or is a child window within their parents own "vector", and visually contained entirely by it's parent, you can use the iteration order in reverse. So to "hit test" the GUI (find the top item for some X,Y, e.g. for a mouse click) you iterate the tree in depth order looking for windows that contain the X,Y (Win32 WindowFromPoint or ChildWindowFromPoint).


Rect rect; // Relative to parent
Widget *parent;
std::vector<std::unique_ptr<Widget>> children;
Widget *Widget::find_solid_widget_at(Vector2F pos)
{
    if (rect.contains(pos))
    {
        // Look for child
        auto client_pos = pos - rect.pos;
        for (auto &w : reverse(children))
        {
            auto n = w->find_solid_widget_at(client_pos);
            if (n) return n;
        }
        return this;
    }
    else
    {
        // Didn't click on this at all
        return nullptr;
    }
}
class Button : public Widget
{
    virtual void on_mouse_down(MouseEvt &evt)override
    {
        // Actually a lot more complex! Win32 style buttons are clicked if BOTH the mouse down AND up are on the button.
        // In many cases there is also a difference between click and drag.
        focus();
        on_click();
    }
};
class TextCtrl : public Widget
{
    virtual void on_mouse_down(MouseEvt &evt)override
    {
        focus();
    }
    virtual void on_char(CharEvt &evt)override
    {
        value += evt.chr;
    }
    std::string value;
};


void ui::Manager::on_mouse_down(MouseEvt &evt)
{
    auto widget = root->find_solid_widget_at(evt.pos);
    if (widget)
    {
        evt.pos -= widget->offset_pos(); // Make the position relative to the widgets cilent position, not the program.
        widget->on_mouse_down(evt);
    }
};
void ui::Manager::on_key_down(KeyEvt &evt)
{
    if (focus)
    {
         focus->on_key_down(evt);
    }
};

 

On 4/2/2019 at 2:49 AM, maltman said:

Focused Item: Focused Item (correct me if I am wrong) is like a text input box when it is ready to receive input.

Focused Window: Like in Windows, entities you can alt-tab through which when focused on will be sent to top of render order. These are all children of entity#0.

Yes keyboard focus is actually separate from the Z-order of children / mouse hit test. It will generally the the top-most top level window. For child windows that can accept keyboard input (e.g. text boxes but not,background panels, static text, images, etc.) focus is often set by a mouse click, but that part does not change the Z order at all. Each top level window has its own "keyboard focus" reference with a get/set.

You can get away with simplifying this to a global keyboard focus (like my code snippet) at the cost of alt-tab like behavior loosing the specifically focused items. But in a game I feel that rarely matters and outside a game you almost certainly want to be using real Win32/native windows as the top level (so they can be dragged around, outside your main app window, reordered, etc. along with the rest of the desktop).

This topic is closed to new replies.

Advertisement