Direct2D, draw partial geometry how to implement scrolling area with text and geometry

[2021/02/26 Update]

These is a much easier way to handle this kind of cutting problem. Just use another layer with mask to clip the drawing. Here is the example:

HRESULT hr;
ID2D1Layer* layer = NULL;

hr = target->CreateLayer(NULL, &layer);

if (SUCCEEDED(hr))
{
    target->PushLayer(
        D2D1::LayerParameters(D2D1::InfiniteRect(), geomatry_mask),
        layer
    );

    target->DrawGeometry(geometry_target, brush);
    target->PopLayer();
}


if (layer != NULL)
    layer->Release();

[Old]

下圖為例,如果在 scrolling area 我們有個文字並用 ID2Geometry 圍起來的區域,在往上或往下時,會需要切掉一部分。

文字部分的剪裁在 https://www.yhorng.com/blog/?p=339 這篇裡面有說明,需要用 callback 來處哩。

Geometry 的剪裁比較沒那麼直接,如果 D2D1_COMBINE_MODE_INTERSECT Combine 原始的圖片和一個外框,

上圖的虛線和實線結合後,用 Draw 會多畫了最上方的那條線,比較一開頭的圖,我們並不希望 “建構者” 三個字上面有畫一條線。

如果我們依然要使用 general solution 的 Geometry 來畫圖,而不是每次遇到不同圖形都要另外客製一條一條線自己畫 (想像一下如果非 rectangle 時有多難搞),該怎麼辦呢?

方法是與其用一個 Rectangle Geometry,我們使用二個來組成 Geometry Group。

上圖左邊實體的黑色 Rectangle 是二個 Rectangle Group,跟綠色的 Geometry 結合後,會變成右邊的圖形,由於 Direct2D 座標和大小都是浮點,所以可以畫得比預設的 1.0f 還細也沒問題。

pseudo-code

HRESULT hr;
ID2D1GeometrySink* sink = NULL;
ID2D1GeometryGroup *frame = NULL;
ID2D1PathGeometry* frame_cut = NULL;
ID2D1RectangleGeometry *rect_outer = NULL, *rect_inner = NULL, *rect_cut = NULL;
ID2D1Geometry* rect_array[2];

factory->CreateRectangleGeometry(D2D1::RectF(left, top, right, bottom), &rect_outer);
factory->CreateRectangleGeometry(D2D1::RectF(
    left + stroke_width, 
    top + stroke_width, 
    right - stroke_width, bottom - stroke_width), 
    &rect_inner);

rect_array[0] = rect_outer;
rect_array[1] = rect_inner;

factory->CreateGeometryGroup(
    D2D1_FILL_MODE_ALTERNATE,
    rect_array,
    ARRAYSIZE(rect_array),
    &frame
);

target->FillGeometry(frame, brushes.get(target, brush);

factory->CreateRectangleGeometry(D2D1::RectF(
    left,
    top > low_pos + y ? top : low_pos + y,
    right,
    bottom < high_pos + y ? bottom : high_pos + y), &rect_cut); 

factory->CreatePathGeometry(&frame_cut);
frame_cut->Open(&sink);
frame->CombineWithGeometry(rect_cut, D2D1_COMBINE_MODE_INTERSECT, NULL, sink);

sink->Close();

target->FillGeometry(frame_cut, brush);