[GWork] 컨트롤 Position 적용 안되는 이슈

Gwork에는 Position을 통해서 쉽게 정렬 처리를 할 수 있습니다. 해당 기능을 사용하다가 제대로 작동하지 않아 분석, 처리한 내용을 정리해봅니다.

enum class Position
{
None        = 0,
Left        = (1<<1),
Right       = (1<<2),
Top         = (1<<3),
Bottom      = (1<<4),
CenterV     = (1<<5),
CenterH     = (1<<6),
Fill        = (1<<7),
Center      = CenterV | CenterH
};

SetPosition시 Position과 패딩값에따라 정렬 처리를 해줍니다.

void Base::SetPosition(Position pos, int xpadding, int ypadding)
{
    const Rect& bounds = GetParent()->GetInnerBounds();
    const Margin& margin = GetMargin();
    int x = X();
    int y = Y();

    if (pos & Position::Left)
        x = bounds.x + xpadding + margin.left;

    if (pos & Position::Right)
        x = bounds.x + (bounds.w - Width() - xpadding - margin.right);

    if (pos & Position::CenterH)
        x = bounds.x + (bounds.w - Width())/2;

    if (pos & Position::Top)
        y = bounds.y + ypadding;

    if (pos & Position::Bottom)
        y = bounds.y + (bounds.h - Height() - ypadding);

    if (pos & Position::CenterV)
        y = bounds.y + (bounds.h - Height())/2 + ypadding;

    SetPos(x, y);
}


아예 Gwk::Controls::Layout 네임스페이스에 Center라는 객체도 지원합니다.

class GWK_EXPORT Center : public CalcPosition
{
GWK_CONTROL_INLINE(Center, CalcPosition)
{
SetPosition(Position::Center);
}
};


아래와 같이 루트 ui 객체인 캔버스를 부모로 해서 center를 생성 후 텍스트 박스를 붙여주면 가운데 정렬로 잘 붙습니다. 샘플에서도 이 Center 객체를 활용해서 Dock 처리를 하고 있더군요.

auto center = new Gwk::Controls::Layout::Center(canvas);
center->SetSize(screenWidth, screenHeight);

auto textBox = new Gwk::Controls::TextBox(center);
textBox->SetPos(100, 500);



다음으로 버튼 하나를 똑같이 center의 자식으로 붙이되 Align::PlaceBelow를 사용해서 textBox보다 50 밑으로 붙게 해줬습니다.

auto button = new Gwk::Controls::Button(center);
button->SetText("CenterBtn");
button->SetSize(75, 30);
Gwk::Align::PlaceBelow(button, textBox, 50);


그런데 아래와 같이 제대로 작동하지 않더군요.


많이 테스트해보진 않았지만 Center 객체를 사용해야하는 경우는 Modal 윈도우 처럼 가운데 정렬이 되는 윈도우 컨테이너가 한개 있을 때 만 사용해야 하는 것 같습니다.

Center를 버리고(사실 샘플에서 그리 사용했지 실 게임 개발에는 모든 UI가 Center가 아니므로 이놈을 부모로 할 필요는 없죠) 캔버스를 루트로 사용해서 테스트를 해봤습니다. 먼저 윈도우 샘플에 있던 소스를 가져와서 아래와 같이 2개를 생성해봤습니다. 일반 윈도우와 모달 윈도우입니다.

auto window1 = new Gwk::Controls::WindowControl(canvas);
window1->SetTitle("Window");
window1->SetSize(200 + rand() % 100, 200 + rand() % 100);
window1->SetPos(100, 100);
window1->SetDeleteOnClose(true);
window1->SetPosition(Gwk::Position::Center);

auto button = new Gwk::Controls::Button(window1);
button->SetText("Click!");


auto window2 = new Gwk::Controls::WindowControl(canvas);
window2->SetTitle("Window modal");
window2->SetSize(200 + rand() % 100, 200 + rand() % 100);
window2->SetPos(100, 100);
window2->MakeModal(true);
window2->SetDeleteOnClose(true);
window2->SetPosition(Gwk::Position::Center);

auto button2 = new Gwk::Controls::Button(window2);
button2->SetText("Click!");

결과는 아래와 같습니다. 모달은 제대로 가운데 정렬이 되지만 일반 윈도우의 경우는 SetPos로 지정한 위치로도 안되고 0, 0에 위치하고 있네요. 둘의 차이점은 MakeModal(true) 밖에 없습니다.



window1에 MakeModal를 활성화 해주면 역시 모달 윈도우처럼 가운데로 잘 나오긴 하지만 이건 제가 바라던게 아니죠. 그래서 Modal 객체를 파보니 이런게 보이네요.

// RecurseLayout() may not be called before Position is set.
m_innerBounds = m_bounds;

RecurseLayout ()은 Position이 설정되기 전에 호출되지 않을 수 있다라는 건데 생성자에서 m_innerBounds를 일반 바운드 값으로 초기화 해주네요. m_innerBounds는
Gwk::Controls::Base에 있습니다.

public:
//! InnerBounds is the area inside the control that
//! doesn't have child controls docked to it.
virtual const Gwk::Rect& GetInnerBounds() const { return m_innerBounds; }

protected:
Gwk::Rect m_innerBounds;

InnerBounds는 자식 컨트롤이 도킹되어 있지 않은 컨트롤 내부의 영역이라고 합니다. 맨 처음에도 나왔지만 Base::SetPosition을 호출 할 때 부모의 innerbounds를 얻어와서 계산을 하고 있습니다.

void Base::SetPosition(Position pos, int xpadding, int ypadding)
{
    const Rect& bounds = GetParent()->GetInnerBounds();
    const Margin& margin = GetMargin();
    int x = X();
    int y = Y();

    if (pos & Position::Left)
        x = bounds.x + xpadding + margin.left;

    if (pos & Position::Right)
        x = bounds.x + (bounds.w - Width() - xpadding - margin.right);

    if (pos & Position::CenterH)
        x = bounds.x + (bounds.w - Width())/2;

    if (pos & Position::Top)
        y = bounds.y + ypadding;

    if (pos & Position::Bottom)
        y = bounds.y + (bounds.h - Height() - ypadding);

    if (pos & Position::CenterV)
        y = bounds.y + (bounds.h - Height())/2 + ypadding;

    SetPos(x, y);
}

그래서 InnerBounds를 설정하는 곳을 찾아보니 Modal 객체 생성자 부분과 void Base::RecurseLayout(Skin::Base* skin) 부분 2군데 뿐이 없네요.


RecurseLayout 는 protected이기 때문에 외부에서 사용할 수 없고 DoThink()를 사용해야할 것 같습니다. 일단 캔버스의 자식들을 정렬시키려면 아래와 같이 캔버스를 생성하고 DoThink를 한번 호출해주면 됩니다. 그러면 캔버스는 innerbounds가 설정이 되니 자식들의 SetPosition을 처리할 때 정상 작동을 해줍니다. 샘플은 이런식으로 안 되어 있어서 있고 Dock과 Center를 활용해서 좀 다르게 되어 있어서 이렇게 분석하고 있네요.

Gwk::Controls::Canvas* canvas = new Gwk::Controls::Canvas(&skin);
canvas->SetSize(screenWidth, screenHeight);
canvas->SetDrawBackground(true);
canvas->SetBackgroundColor(Gwk::Color(150, 170, 170, 255));
canvas->DoThink();


그 외에 캔버스의 자식으로 붙는 Base를 상속 받는 커스텀 윈도우 컨테이너에 여러 자식들을 정렬시키고자 한다면 해당 객체를 초기화 후 내부에서 직접 m_innerBounds = m_bounds; 를 해주던가 하면 됩니다.

좀더 근본적으로는 Base::SetSize에서 m_bounds를 설정하므로 Base::SetBounds를 적절히 오버라이드해서 m_innerBounds = m_bounds; 를 해주면 될 것 같네요.

bool Base::SetSize(int w, int h)
{
    return SetBounds(X(), Y(), w, h);
}

bool Base::SetBounds(const Gwk::Rect& bounds)
{
    if (m_bounds == bounds)
        return false;

    const Gwk::Rect oldBounds = GetBounds();
    m_bounds = bounds;
    OnBoundsChanged(oldBounds);
    return true;
}

댓글

이 블로그의 인기 게시물

CMake Windows에 설치하기

'xxx.exe' 프로그램을 시작할 수 없습니다. 지정된 파일을 찾을 수 없습니다.

크로스 스레드 작업이 잘못되었습니다. xxx 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다