buggins / dlangui

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

How to set widget width in pixels? #654

Closed bagomot closed 1 year ago

bagomot commented 1 year ago

I have a mainWidget that inherits from FrameLayout and takes up the full width of the window. Inside it is a VerticalLayout. This VerticalLayout should be 400px width, but it always stretches to the full width of the parent widget. I looked at the examples and source code, but couldn't figure out how to get the width I wanted. Help me please.

GrimMaple commented 1 year ago

Hi! I'm not sure what the exact problem could be, as I still don't really understand the layouting process. I think, it might be unrelated to the physical side of the vidget and its size -- I've seen that dlangui makes no effor to actually prevent rendering outside of physical widget borders.

Could you provide some sample code so I can at least debug it in any way?

bagomot commented 1 year ago

Hi! I'm not sure what the exact problem could be, as I still don't really understand the layouting process. I think, it might be unrelated to the physical side of the vidget and its size -- I've seen that dlangui makes no effor to actually prevent rendering outside of physical widget borders.

Could you provide some sample code so I can at least debug it in any way?

Here is a code example that demonstrates incorrect behavior:

#!/usr/bin/env dub
/+ dub.sdl:
    dependency "dlangui" version="~>0.10.2"
+/

import dlangui;

mixin APP_ENTRY_POINT;

extern (C) int UIAppMain(string[] args)
{
    auto window = Platform.instance.createWindow("Test"d, null, WindowFlag.Resizable, 720, 405);

    window.mainWidget = new MainFrame();
    window.show;

    return Platform.instance.enterMessageLoop();
}

class MainFrame : FrameLayout
{
    this()
    {
        backgroundColor("white");

        auto text = new TextWidget(null, "Test text"d);
        text.textColor("white");

        VerticalLayout vLayout = new VerticalLayout();
        vLayout.alignment = Align.Center;
        vLayout.layoutWidth(400); // ?
        vLayout.maxWidth(400); // ?
        vLayout.backgroundColor("black");
        vLayout.layoutWidth(WRAP_CONTENT);
        vLayout.margins(20);

        vLayout.addChild(text);

        addChild(vLayout);
    }
}
GrimMaple commented 1 year ago

OK, so, after debugging a little, here's what I came up with.

I think the layout engine is trying to "claim" all available space for it, as a vertical layout of 400 makes no sense in a parent layout of FILL_PARENT. However, if you add two layouts, then the width becomes as expected. Here's the layout I used:

import dlangui;

mixin APP_ENTRY_POINT;

extern (C) int UIAppMain(string[] args)
{
    auto window = Platform.instance.createWindow("Test"d, null, WindowFlag.Resizable, 720, 405);

    window.mainWidget = new MainFrame();
    window.show;

    return Platform.instance.enterMessageLoop();
}

class MainFrame : FrameLayout
{
    this()
    {
        super("FRAME");
        backgroundColor("white");

        auto text = new TextWidget("TEXT", "Test text"d);
        text.textColor("white");
        text.backgroundColor("red");
        text.minWidth(200);

        auto vLayout = new HorizontalLayout("HORZ");
        vLayout.alignment = Align.Center;
        vLayout.layoutWidth(400); // ?
        vLayout.minWidth(400); // ?
        vLayout.backgroundColor("black");
        vLayout.layoutWidth(WRAP_CONTENT);
        vLayout.margins(20);

        vLayout.addChild(text);
        auto abc = new VerticalLayout();
        abc.layoutWidth(FILL_PARENT);
        vLayout.addChild(abc);

        addChild(vLayout);
        auto extraLayout = new VerticalLayout();
        extraLayout.layoutWidth(FILL_PARENT);
        auto base = new HorizontalLayout();
        base.addChild(vLayout);
        base.addChild(extraLayout);
        addChild(base);
    }
}

NOTE: I'm using minWidth instead of maxWidth to make it exactly 400 pixels wide.

I'm not sure if this is a bug or a feature, but at least you can work this around if needed. I'm inclined to believe that it's indeed a feature, because the proposed layout makes very little sense to me.

The decision is tough, very tough.

GrimMaple commented 1 year ago

UP: I made a new discovery -- you don't have to do the trickery, you just need to have a layout inside a layout. Otherwise it seems to be trying to fill parent regardless. This works just as fine:

class MainFrame : FrameLayout
{
    this()
    {
        super("FRAME");
        backgroundColor("white");

        auto text = new TextWidget("TEXT", "Test text"d);
        text.textColor("white");
        text.backgroundColor("red");
        text.layoutWidth(200);

        auto vLayout = new HorizontalLayout("VERT");
        vLayout.alignment = Align.Center;
        vLayout.layoutWidth(400);
        vLayout.backgroundColor("black");
        vLayout.margins(20);

        vLayout.addChild(text);

        addChild(vLayout);
        auto extraLayout = new VerticalLayout();
        auto base = new HorizontalLayout();
        base.addChild(vLayout);
        addChild(base);
    }
}

However, changing addChild(base) to addChild(vLayout) results in vLayout being stretched to the window's width.

bagomot commented 1 year ago

UP: I made a new discovery -- you don't have to do the trickery, you just need to have a layout inside a layout. Otherwise it seems to be trying to fill parent regardless. This works just as fine:

class MainFrame : FrameLayout
{
    this()
    {
        super("FRAME");
        backgroundColor("white");

        auto text = new TextWidget("TEXT", "Test text"d);
        text.textColor("white");
        text.backgroundColor("red");
        text.layoutWidth(200);

        auto vLayout = new HorizontalLayout("VERT");
        vLayout.alignment = Align.Center;
        vLayout.layoutWidth(400);
        vLayout.backgroundColor("black");
        vLayout.margins(20);

        vLayout.addChild(text);

        addChild(vLayout);
        auto extraLayout = new VerticalLayout();
        auto base = new HorizontalLayout();
        base.addChild(vLayout);
        addChild(base);
    }
}

However, changing addChild(base) to addChild(vLayout) results in vLayout being stretched to the window's width.

It works, now the size is taken into account. But another problem appeared: now the center alignment (vLayout.alignment = Align.Center;) does not work (even if it is applied to all containers).

bagomot commented 1 year ago

I was able to make a working version of the alignment for my layout. This works by overriding the layout method for the layout where the widgets that need to be aligned will be placed:

override void layout(Rect rc)
 {
            _needLayout = false;
            if (visibility == Visibility.Gone)
            {
                return;
            }
            _pos = rc;
            applyMargins(rc);
            applyPadding(rc);

            if (_children.length == 0)
            {
                return;
            }

            // Layout the children
            foreach (child; _children)
            {
                // Measure the child
                child.measure(rc.width, rc.height);

                // Calculate the starting x and y positions
                int startX;
                if (alignment() & Align.Left)
                {
                    startX = rc.left;
                }
                else if (alignment() & Align.Right)
                {
                    startX = rc.right - child.measuredWidth;
                }
                else // default to center alignment
                {
                    startX = rc.left + (rc.width - child.measuredWidth) / 2;
                }

                int startY;
                if (alignment() & Align.Top)
                {
                    startY = rc.top;
                }
                else if (alignment() & Align.Bottom)
                {
                    startY = rc.bottom - child.measuredHeight;
                }
                else // default to center alignment
                {
                    startY = rc.top + (rc.height - child.measuredHeight) / 2;
                }

                // Calculate the end x and y positions
                int endX = startX + child.measuredWidth;
                int endY = startY + child.measuredHeight;

                // Create a new rectangle for the child
                auto childRect = Rect(Point(startX, startY), Point(endX, endY));

                // Layout the child
                child.layout(childRect);
            }
}

This won't work if nested widgets need to be resized to fit the window.