スポンサーリンク

矩形をxmlで再帰的に分割(2)

前回矩形を分割するプログラムを書いたのだが、親要素に対する割合でしか指定するように作っていなかったことに気づいた。なのでピクセル単位での指定もできるようにした。

#include <windows.h>

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

// 領域の計算方法
enum class SizeType {
  RATIO, // 親要素に対する割合で指定
  PIXEL, // ピクセル数で指定
  AUTO,  // 余っているサイズから自動計算
};

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

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

struct BOX {
  SizeType sizeType;     // この領域のサイズの指定方法
  Arrangement type;      // 子要素の配置方法
  Rect rect;         // この領域の矩形
  std::string name;      // この領域の名前
  // ratioとpixelはどちらか一方が指定される
  double ratio;        // この領域が親要素のどれだけの割合を占めるか
  int pixel;         // この領域が親要素のどれだけのピクセルを占めるか
  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()   );
  node.pixel = xmlNode.attribute("pixel").as_int(-1);

  const auto& attr_ratio = xmlNode.attribute("ratio");
  const auto& attr_pixel = xmlNode.attribute("pixel");
  // "ratio" および "pixel" が存在しない場合はAUTO
  if (attr_ratio.empty() && attr_pixel.empty() ) {
    node.sizeType = SizeType::AUTO;
  }
  // "ratio" が存在し、かつ内容が"auto"であればAUTO
  else if (!attr_ratio.empty() && std::string(attr_ratio.as_string()) == "auto") {
    node.sizeType = SizeType::AUTO;
  }
  // "pixel" が存在し、かつ内容が"auto"であればAUTO
  else if (!attr_ratio.empty() && std::string(attr_pixel.as_string()) == "auto") {
    node.sizeType = SizeType::AUTO;
  }
  else if (!attr_ratio.empty()) {
    node.sizeType = SizeType::RATIO;
  }
  else if (!attr_pixel.empty()) {
    node.sizeType = SizeType::PIXEL;
  }
  else {
    node.sizeType = SizeType::AUTO;
  }


  // 配置方法を取得。デフォルトを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;
}

// UpdateLayout関数
void UpdateLayout(BOX& node, const Rect& parent) {
  node.rect = parent;
  int totalWidth = parent.ex - parent.sx;
  int totalHeight = parent.ey - parent.sy;

  int total;
  if (node.type == Arrangement::Horizonal) {
    // 水平方向に並べる場合
    total = totalWidth;
  }
  else {
    total = totalHeight;
  }

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

  int undefinedRatioCount = 0; // ratioが定義されていないboxの個数
  size_t last_item_index;
  // まず、指示のある領域のサイズを計算する
  // タイプがpixelの場合は、そのままpixelを使う
  // タイプがratioの場合は、親要素の割合を使い、ピクセルに変換する
  // タイプが指定されていない場合は、その数をカウントし、childを記憶しておく

  // totalから確定したサイズを引いた残りをピクセルで計算
  int remainingPixel = total;
  std::vector<int> sizes(node.children.size());
  for (size_t child_id = 0; child_id < node.children.size(); child_id++) {
    switch (node.children[child_id].sizeType) {
    case SizeType::PIXEL:
      // pixelが指定されている場合はそのまま使う
      sizes[child_id] = node.children[child_id].pixel;
      remainingPixel -= node.children[child_id].pixel; // 残りのピクセル数を計算
      break;
    case SizeType::RATIO:
      sizes[child_id] = node.children[child_id].ratio * total;
      remainingPixel -= sizes[child_id]; // 残りのピクセル数を計算
      break;
    case SizeType::AUTO:
      sizes[child_id] = -1; // 未定義の場合は-1にしておく
      undefinedRatioCount++;
      last_item_index = child_id;
      break;
    }
  }

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

    // 未定義のノードに割り当てる割合を計算
    // 残っている使えるピクセル数 / サイズ未定義のノード数
    const double equaldivision = (double)remainingPixel / (double)undefinedRatioCount;

    int sizesum = 0;
    // 全てのノードに割り当てた場合のピクセル数を計算
    for (size_t child_id = 0; child_id < node.children.size(); child_id++) {
      if (sizes[child_id] == -1) {
        sizesum += equaldivision;
      }
      else {
        sizesum += sizes[child_id];
      }
    }
    // 最後のノードで誤差を吸収
    // 分割したピクセル数の合計が親要素のピクセル数より大きければマイナスになる
    int lastitem_error = total - sizesum;
    // 実際にサイズを与える
    for (size_t child_id = 0; child_id < node.children.size(); child_id++) {
      if (sizes[child_id] == -1) {
        sizes[child_id] = equaldivision;
      }
    }
    sizes[last_item_index] += lastitem_error;

  }

  // 計算した長さを子要素の矩形に与える
  int offset=0;
  for (size_t child_id = 0; child_id < node.children.size(); child_id++) {
    auto& child = node.children[child_id];

    if (child_id == 0) {
      offset=0;
    }
    else {
      offset += sizes[child_id-1];
    }

    if (node.type == Arrangement::Horizonal) {
      child.rect.sx = parent.sx + offset;
      child.rect.ex = child.rect.sx + sizes[child_id];
      child.rect.sy = parent.sy;
      child.rect.ey = parent.ey;
    }
    else {
      child.rect.sy = parent.sy + offset;
      child.rect.ey = child.rect.sy + sizes[child_id];
      child.rect.sx = parent.sx;
      child.rect.ex = parent.ex;
    }

  }

  // 子要素の子要素の矩形を計算する
  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;

  if (node.type == Arrangement::Horizonal) {

    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); // ペンを削除
  }
  else {
    // 塗りつぶした矩形
    HBRUSH hBrush = CreateSolidBrush(RGB(r, g, b)); // ブラシを作成
    HGDIOBJ oldBrush = SelectObject(hdc, hBrush); // ブラシ
    Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
    SelectObject(hdc, oldBrush); // 元のブラシに戻す
    DeleteObject(hBrush); // ブラシを削除
  }



  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_CREATE:
    GetClientRect(hwnd, &rect);
    UpdateLayout(rootArea, Rect{ 0, 0, rect.right, rect.bottom });
    InvalidateRect(hwnd, nullptr, TRUE);
    return 0;
  case WM_SIZING:
    GetClientRect(hwnd, &rect);
    UpdateLayout(rootArea, Rect{ 0, 0, rect.right, rect.bottom });

    InvalidateRect(hwnd,nullptr,TRUE);
    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"(C:\test\layout3.xml)");
  UpdateLayout(rootArea, rootRect);


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

  if (hwnd == NULL) return 0;

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

layout3.xml

<?xml version="1.0" encoding="utf8" ?>
<box name="main" ratio="1.0" arrangement="horizonal">
  <box name="menu" pixel="300" arrangement="vertical">

    <box name="menu-top" pixel="50" arrangement="horizonal">
      <box name="area-a" pixel="50" arrangement="vertical"></box>
      <box name="area-b" pixel="50" arrangement="vertical"></box>
      <box name="area-c" ratio="auto" arrangement="vertical"></box>
    </box>

    <box name="menu-middle" ratio="0.2" arrangement="horizonal"></box>
    <box name="menu-bottom"             arrangement="horizonal"></box>

  </box>
    
  <box name="containts"  ratio="auto" arrangement="vertical" >
  </box>

</box>

実行結果

コメントを残す

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

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


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