スポンサーリンク

矩形をxmlで再帰的に分割

UIを簡易的に定義したいので、自作xmlとそれに基づいて領域を計算するコードを書く。

XML

まずXMLを定義。

nameは識別子だが一意である必要はない。arrangementは子要素の配置方法。ratioは親要素に対するその要素の割合。

<?xml version="1.0" encoding="utf8" ?>
<box name="main" ratio="1.0" arrangement="horizonal">
  <box name="red" ratio="0.3" arrangement="vertical">
     <box name="area1"></box>
     <box name="area2"></box>
     <box name="area3" ratio="0.5"></box>
  </box>
    
  <box name="blue"  ratio="0.6" arrangement="vertical" >
     <box name="area1" ratio="0.2"></box>
     <box name="area2" ratio="0.8"></box>
  </box>

  <box name="green" ratio="0.1" arrangement="vertical" >
     <box name="area1" ratio="0.3"></box>
     <box name="area2" ratio="0.3"></box>
     <box name="area3" ratio="0.3"></box>
     
     <box name="area4" ratio="0.1"  arrangement="horizonal">
       <box name="target1" ratio="0.3"></box>
       <box name="target2" ></box>
     </box>
  </box>
</box>

コード

#include <windows.h>

#include <iostream>
#include <string>
#include <vector>
#include <pugixml.hpp>
#include <functional>

// 子要素の配置方法
enum class Arrangement {
    Horizonal,
    Vertical,
};

// 各情報を元に計算される実際のサイズ。sx,sy, ex,eyは矩形の最小、最大を表すピクセル座標
struct Rect {
    int sx, sy;
    int ex, ey;
};

struct BOX {
    Arrangement type;          // 子要素の配置方法
    Rect rect;                 // この領域の矩形
    std::string name;          // この領域の名前
    double ratio;              // この領域が親要素のどれだけの割合を占めるか
    std::vector<BOX> children; // 子要素
};

// xmlファイルの内容を解析してノード作成
void parseNode(const pugi::xml_node& xmlNode, BOX& node) {

    // BOXの名前を取得
    node.name = xmlNode.attribute("name").as_string("");
    // BOXの親要素に対する割合を取得。指定がない場合はNaNにしておいて、自動計算する
    node.ratio = xmlNode.attribute("ratio").as_double( std::numeric_limits<double>::quiet_NaN()   );

    // 配置方法を取得。デフォルトをverticalとしておく。
    std::string arrangement = xmlNode.attribute("arrangement").as_string("vertical");
    node.type = (arrangement == "horizonal") ? Arrangement::Horizonal : Arrangement::Vertical;

    // 子要素を再帰的に解析
    for (pugi::xml_node child = xmlNode.first_child(); child; child = child.next_sibling()) {
        BOX childNode;
        parseNode(child, childNode);
        node.children.push_back(childNode);
    }
}
      
      
// xmlファイルからレイアウト情報を読み込む
BOX LoadLayout(const char* xmlfilename) {
    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(xmlfilename);

    // ファイルが読み込めなかったら例外を投げる
    if (!result) {
        throw std::runtime_error("xmlファイルが読み込めませんでした");
    }

    BOX rootNode;
    pugi::xml_node root = doc.child("box");
    parseNode(root, rootNode);

    return rootNode;
}
      
      
// 読み込んだxmlファイルから領域を計算
void UpdateLayout(BOX& node, const Rect& parent) {
    node.rect = parent;
    int totalWidth = parent.ex - parent.sx;
    int totalHeight = parent.ey - parent.sy;

    /////////////////////////////////
    // 親要素を占める割合の計算
    // ratioを省略可能としている
    // ratioが省略されたときは、その領域内の残りのratioを省略されたbox数で割る

    double remainingRatio = 1.0; // 1.0 = 100% parent全体の割合を1.0とする
    int undefinedRatioCount = 0; // ratioが定義されていないboxの個数

    for (auto& child : node.children) {

        if (std::isnan(child.ratio) == true) {

            // ratioが定義されていないノードの個数を知りたいのでここで確認する
            undefinedRatioCount++;

        }
        else {
            // ratioが定義されているノードの割合を引き、
            // ratio未定義のboxが使用可能な割合を計算する
            remainingRatio -= child.ratio;
        }

    }

    // ratioが定義されていないノードがある場合は、使用可能な割合を等分する
    if (undefinedRatioCount > 0) {
        // ratioが定義されていないノードは、使用可能な量を等分する
        double defaultRatio = remainingRatio / undefinedRatioCount;

        // ratio未定義のノードにratioを与える
        for (auto& child : node.children) {
            if (std::isnan(child.ratio) == true) {
                child.ratio = defaultRatio;
            }
        }
    }

    // 子要素の矩形を計算する
    double accumRatio = 0.0;
    for (auto& child : node.children) {
        if (node.type == Arrangement::Horizonal) {
            child.rect.sx = parent.sx + totalWidth * accumRatio;
            child.rect.ex = child.rect.sx + totalWidth * child.ratio;
            child.rect.sy = parent.sy;
            child.rect.ey = parent.ey;
        }
        else {
            child.rect.sy = parent.sy + totalHeight * accumRatio;
            child.rect.ey = child.rect.sy + totalHeight * child.ratio;
            child.rect.sx = parent.sx;
            child.rect.ex = parent.ex;
        }
        accumRatio += child.ratio;

    }

    // 子要素の子要素の矩形を計算する
    for (auto& child : node.children) {
        UpdateLayout(child, child.rect);
    }
}

テストコード

// 領域を描画
void drawRects(HDC hdc, const BOX& node) {

    
    RECT rect = { node.rect.sx, node.rect.sy, node.rect.ex, node.rect.ey };
    
    // ランダムな色を作成
    int r = rand() % 256;
    int g = rand() % 256;
    int b = rand() % 256;


    HPEN hPen = CreatePen(PS_SOLID, 1, RGB(r, g, b)); // 1ピクセル幅のペンを作成
    HGDIOBJ oldPen = SelectObject(hdc, hPen); // ペンを選択

    // 矩形を描画
    Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);

    SelectObject(hdc, oldPen); // 元のペンに戻す
    DeleteObject(hPen); // ペンを削除



    for (const BOX& child : node.children) {
        drawRects(hdc, child);
    }
}


BOX rootArea;


LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rect;

    switch (msg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        GetClientRect(hwnd, &rect);
        FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));

        drawRects(hdc, rootArea);// テスト用 矩形描画

        EndPaint(hwnd, &ps);
        return 0;
	}
    return DefWindowProc(hwnd, msg, wp, lp);
}

int WINAPI WinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance,
    PSTR lpCmdLine, 
    int nCmdShow) {
    HWND hwnd;
    WNDCLASS winc;
    MSG msg;

    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = WndProc;
    winc.cbClsExtra = winc.cbWndExtra = 0;
    winc.hInstance = hInstance;
    winc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    winc.hCursor = LoadCursor(NULL, IDC_ARROW);
    winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    winc.lpszMenuName = NULL;
    winc.lpszClassName = TEXT("SZL-WINDOW");

    if (!RegisterClass(&winc)) return 0;


    Rect rootRect{ 0, 0, 800, 300 };
    rootArea = LoadLayout(R"(layout2.xml)");// レイアウト読み込み
    UpdateLayout(rootArea, rootRect);// 矩形を計算


    hwnd = CreateWindow(
        TEXT("SZL-WINDOW"), TEXT("test window"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL
    );

    if (hwnd == NULL) return 0;

    while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg);
    return msg.wParam;
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: