buggins / dlangui

Cross Platform GUI for D programming language
Boost Software License 1.0
815 stars 121 forks source link

Getting an OpenGL widget to work #680

Open jeannekamikaze opened 10 months ago

jeannekamikaze commented 10 months ago

I am unable to get an OpenGL widget working. I used opengl.d as a reference and I noticed that if I don't specify a padding or margins, the widget has 0 size. Moreover, the rect that gets passed in does not seem to map to the widget's location on the screen anyway.

Below is a minimal example where nothing gets drawn. If I replace the OpenGL widget with a button, however, the button does appear on the screen.

Any help on getting this example to work would be appreciated. Thank you.

import bindbc.opengl;
import dlangui;

mixin APP_ENTRY_POINT;

class MainView : VerticalLayout
{
    this(string id)
    {
        super(id);

        layoutWidth = FILL_PARENT;
        layoutHeight = FILL_PARENT;

        backgroundDrawable = DrawableRef(new OpenGLDrawable(&render));
    }

    void render(Rect windowRect, Rect rc)
    {
        // Clear the widget to pink.
        glEnable(GL_SCISSOR_TEST);
        glScissor(rc.left, rc.bottom, rc.width, rc.height);
        glClearColor(1, 0, 1, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_SCISSOR_TEST);
    }
}

extern (C) int UIAppMain(string[] args)
{
    Window window = Platform.instance.createWindow("Game Maker", null);

    // Nothing gets rendered. The widget seems to have 0 size.
    window.mainWidget = new MainView("MainView");

    // The button does appear on the screen.
    //window.mainWidget = new Button("Button", "Click me"d);

    window.show();
    return Platform.instance.enterMessageLoop();
}
jeannekamikaze commented 10 months ago

Answering part of my own question: the rect's coordinates appear to be in the window's coordinate system, with the origin at the top left, versus OpenGL's bottom left. Therefore, to get the example above working, one must subtract the rect's bottom from the window's height when computing the rect's y-origin:

glScissor(rc.left, windowRect.height - rc.bottom, rc.width, rc.height);

There is still a problem with the widget's size when it is embedded inside a layout, however, which is the situation I would expect in most non-trivial applications. The following is a minimal example that reproduces this problem.

If you leave the padding = 1 line commented out in the example, you'll see that the widget gets a height of 0. If you uncomment it, then the widget gets the correct height. The same is true if you set the margin instead of the padding, or if you set both. Note that the value per se of the padding or margin does not really matter; it's the fact that it is non-zero that makes the widget work.

import bindbc.opengl;
import dlangui;
import std.stdio;

mixin APP_ENTRY_POINT;

class MainView : FrameLayout
{
    this(string id)
    {
        super(id);

        // Uncomment this line to give the widget a non-zero height.
        //padding = 1;

        layoutWidth = FILL_PARENT;
        layoutHeight = FILL_PARENT;

        backgroundDrawable = DrawableRef(new OpenGLDrawable(&render));
    }

    void render(Rect windowRect, Rect rc)
    {
        writeln(rc);
        writeln("left: ", rc.left, ", bottom: ", rc.bottom, ", width: ", rc
                .width, ", height: ", rc.height);

        // Clear the widget to pink.
        glEnable(GL_SCISSOR_TEST);
        glScissor(rc.left, windowRect.height - rc.bottom, rc.width, rc.height);
        glClearColor(1, 0, 1, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_SCISSOR_TEST);
    }
}

extern (C) int UIAppMain(string[] args)
{
    Window window = Platform.instance.createWindow("Game Maker", null);

    window.mainWidget = parseML(q{
        VerticalLayout {
            id: ViewLayout
            layoutWidth: fill; layoutHeight: fill
        }
    });
    auto viewLayout = cast(VerticalLayout) window.mainWidget.childById("ViewLayout");
    viewLayout.addChild(new MainView("MainView"));

    window.show();
    return Platform.instance.enterMessageLoop();
}