Realization of Flexible Control Arrangement with Weight in Duilib (I)

Keywords: Windows less xml

In the process of developing player software, because the size of windows is variable, in order to make the controls in the control panel (play, previous, next, full screen, subtitles, etc.) display and hide in accordance with the size change of windows, product managers define a series of rules so that the most core functions can be provided to users at any time.


List the needs of the product manager first:
Shrinking from both sides to the middle ensures that the left LOGO and the right X are displayed first.
Hidden Priorities at the Top: Search Bar, Skin Change, Feedback, Play Records, Minimize, Maximize
Bottom Hidden Priority: Full Screen, Enhanced Picture Quality, Traceless, Open File, Play Order, Volume Bar

In the process of dealing with this requirement, the predecessors also tried some methods, comparing with the absolute layout of float, they completely customized by management class. Personally, I hope to achieve this by using Container's own layout algorithm and graceful relative layout. After several days of exploration, this effect can be roughly achieved. Here we share our ideas and implementation.

When analyzing this requirement, I hope to introduce the concept of weight similar to the third dimension, that is, when the position provided by the parent container is not enough to display all the child controls completely, hide them in order of importance from low to high, and free the display of the control to provide more important controls for placement. Simple deduction, it should be feasible, according to this simple. Rules make it easier to address this requirement, and the code is relatively simple to maintain. Only a parent container named CWeightHorizontal Layout UI that can be arranged according to the weight value of the child control is needed.

OK, let's implement this parent container. First, let me introduce the method of CHorizontal LayoutUI:: SetPost.
In fact, it is two for loops, the previous trial calculation, will not set the width of the control recorded, the remaining space for an average, set to the adaptive control, and set the width of the control is arranged according to the set value.

    void CWeightHorizontalLayoutUI::SetPos(RECT rc)
    {
        CControlUI::SetPos(rc);
        rc = m_rcItem;

        std::map<CControlUI*, int /*width*/> mapAjust;
        // Adjust for inset
        rc.left     += m_rcInset.left;
        rc.top      += m_rcInset.top;
        rc.right    -= m_rcInset.right;
        rc.bottom   -= m_rcInset.bottom;

        if (m_items.GetSize() == 0) {
            ProcessScrollBar(rc, 0, 0);
            return;
        }

        if (!m_bScrollFloat && m_pVerticalScrollBar && m_pVerticalScrollBar->IsVisible()) 
            rc.right -= m_pVerticalScrollBar->GetFixedWidth();
        if (!m_bScrollFloat && m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible()) 
            rc.bottom -= m_pHorizontalScrollBar->GetFixedHeight();

        ResetWeightDisplayControlState(); 

//  Read the weight of the current child control
        m_mapWeight.clear();
        for (int i = 0; i < m_items.GetSize(); i++) {
            DuiLib::CControlUI* pControl = static_cast<DuiLib::CControlUI*>(m_items[i]);
            if (pControl == nullptr) continue;
            CStdString strweight = pControl->GetCustomAttribute(L"weight");
            int nweight = _ttoi(strweight.GetData());
            m_mapWeight[i] = nweight;
        }
        m_mapWeightCache = m_mapWeight;


        // Find the Minimum Weight Control  
        auto HideMinWeigth = [&](int index) -> bool {
            CControlUI* pControl = static_cast<CControlUI*>(m_items[index]);
            if (pControl && pControl->IsVisible()) {
                pControl->SetInternVisible(false);
                m_arrWeightHideControl.push_back(pControl);
                return true;
            }
            return false;
        };

        // Determine the width of elements that are sizeable
        SIZE szAvailable = { rc.right - rc.left, rc.bottom - rc.top };
        if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible())
            szAvailable.cx += m_pHorizontalScrollBar->GetScrollRange();

        int nAdjustables = 0;
        int cxFixed = 0;
        int nEstimateNum = 0;
        int cxExpand = 0;

        std::pair<int, int> min_weight; // first:control index, second:weight

        std::map<CControlUI*, int /*width*/> m_mapEstimateWidth;  // Preset Width of Control in Trial
        std::map<CControlUI*, int /*width*/> m_mapBookWidth;
        do {
            // Find the control that has the lowest current weight and is displayed
            min_weight = std::make_pair(999, 999);
            std::for_each(m_mapWeight.begin(), m_mapWeight.end(), [&](std::pair<int, int> item)-> void {
                CControlUI* pControl = static_cast<CControlUI*>(m_items[item.first]);
                if (item.second <= min_weight.second && pControl->IsVisible())
                    min_weight = item;
            });

            // Remove it from the map after it has been found to avoid the next lookup of the control
            auto min_it = std::find_if(m_mapWeight.begin(), m_mapWeight.end(), [min_weight](std::pair<int, int> item)-> bool {
                return (item.first == min_weight.first && item.second == item.second);
            });
            if (min_it != m_mapWeight.end()) m_mapWeight.erase(min_it);

            // Trial calculation
            nAdjustables = 0;
            cxFixed = 0;
            nEstimateNum = 0;
            for (int it1 = 0; it1 < m_items.GetSize(); it1++) {
                CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);
                if (pControl->IsFloat()) continue;
                if (!pControl->IsVisible()) continue;
                SIZE sz = pControl->EstimateSize(szAvailable);
                if (sz.cx == 0) {                                    
                     nAdjustables++;
                }
                else {
                    if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();
                    if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();
                }
                m_mapEstimateWidth[pControl] = sz.cx;
                cxFixed += sz.cx + pControl->GetPadding().left + pControl->GetPadding().right;
                nEstimateNum++;
            }
            cxFixed += (nEstimateNum - 1) * m_iChildPadding;

            cxExpand = 0;
            if (nAdjustables > 0) cxExpand = max(0, (szAvailable.cx - cxFixed) / nAdjustables);


// Explanation in the following section
            // If less than space is not enough, try reducing controls with adjust attributes from low to high first
            if (szAvailable.cx - cxFixed < 0) {
                std::vector<std::pair<CControlUI*, int>> adj_ctrls;
                for (int it1 = 0; it1 < m_items.GetSize(); it1++) {
                    CControlUI* pControl = static_cast<CControlUI*>(m_items[it1]);
                    if (pControl->IsFloat()) continue;
                    //if (!pControl->IsVisible()) continue;
                    if (pControl->GetCustomAttribute(L"adjustwidth") == NULL ||
                        _tcscmp(pControl->GetCustomAttribute(L"adjustwidth"), L"true")) continue;
                    adj_ctrls.push_back(std::make_pair(pControl, m_mapWeightCache[it1]));
                }

                // From small to large by weight, try the minimum weight first.
                std::sort(adj_ctrls.begin(), adj_ctrls.end(), [&](std::pair<CControlUI*, int> lhs,
                    std::pair<CControlUI*, int> rhs) -> bool {
                    return lhs.second < rhs.second;
                });

             
                for (auto it = adj_ctrls.begin(); it != adj_ctrls.end(); it++) {
                    auto ctrl = it->first;
                    if (m_mapEstimateWidth[ctrl] > ctrl->GetMinWidth()) {
                        int sub_width = min(abs(szAvailable.cx - cxFixed), m_mapEstimateWidth[ctrl] - ctrl->GetMinWidth());
                        m_mapEstimateWidth[ctrl] = m_mapEstimateWidth[ctrl] - sub_width;    // New trial width 
                        m_mapBookWidth[ctrl] = m_mapEstimateWidth[ctrl];
                        cxFixed -= sub_width;
                        if (szAvailable.cx - cxFixed >= 0) break; // Arrangement space has been satisfied
                    }                    
                }
            }

            // Space is insufficient to hide from the lowest weight in turn
        } while (szAvailable.cx - cxFixed < 0 && HideMinWeigth(min_weight.first));



        // Position the elements
        SIZE szRemaining = szAvailable;
        int iPosX = rc.left;
        if (m_pHorizontalScrollBar && m_pHorizontalScrollBar->IsVisible()) {
            iPosX -= m_pHorizontalScrollBar->GetScrollPos();
        }
        int iAdjustable = 0;
        int cxFixedRemaining = cxFixed;
        for (int it2 = 0; it2 < m_items.GetSize(); it2++) {
            CControlUI* pControl = static_cast<CControlUI*>(m_items[it2]);
            if (!pControl->IsVisible()) continue;
            if (pControl->IsFloat()) {
                SetFloatPos(it2);
                continue;
            }
            RECT rcPadding = pControl->GetPadding();
            szRemaining.cx -= rcPadding.left;
            SIZE sz = pControl->EstimateSize(szRemaining);
            if (sz.cx == 0) {
                iAdjustable++;
                sz.cx = cxExpand;
                //The last adaptive size need not be calculated separately
                //if( iAdjustable == nAdjustables ) {
                //	sz.cx = MAX(0, szRemaining.cx -cxFixedRemaining);
                //}
                if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();
                if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();
            }
            else {
                if (sz.cx < pControl->GetMinWidth()) sz.cx = pControl->GetMinWidth();
                if (sz.cx > pControl->GetMaxWidth()) sz.cx = pControl->GetMaxWidth();
            }

            sz.cy = pControl->GetFixedHeight();
            if (sz.cy == 0) sz.cy = rc.bottom - rc.top - rcPadding.top - rcPadding.bottom;
            if (sz.cy < 0) sz.cy = 0;
            if (sz.cy < pControl->GetMinHeight()) sz.cy = pControl->GetMinHeight();
            if (sz.cy > pControl->GetMaxHeight()) sz.cy = pControl->GetMaxHeight();

            //padding does not count control width//2012/09/12
            //RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, iPosX + sz.cx + rcPadding.left + rcPadding.right, rc.top + rcPadding.top + sz.cy};
            if (m_mapBookWidth.find(pControl) != m_mapBookWidth.end() &&
                m_mapBookWidth[pControl] != sz.cx) {
                sz.cx = m_mapBookWidth[pControl];
            }
            RECT rcCtrl = { iPosX + rcPadding.left, rc.top + rcPadding.top, min(iPosX + sz.cx + rcPadding.left, rc.right) , rc.top + rcPadding.top + sz.cy };
            pControl->SetPos(rcCtrl);
            iPosX += sz.cx + m_iChildPadding + rcPadding.left + rcPadding.right;
            szRemaining.cx -= sz.cx + m_iChildPadding + rcPadding.right;
            if (m_bWholeDisplay && rcCtrl.right > rc.left + szAvailable.cx)
                m_arrWholeDisplayControl.push_back(pControl);
        }

        // Process the scrollbar
        ProcessScrollBar(rc, 0, 0);
    }

The above code is mainly in the trial calculation process, when the remaining space width is insufficient, hide the smallest weight control, and try again.
It should be noted that the Set Visible interface of the child control can not be invoked directly when Hide, which will change the basic state of the control. Set InternVisible should be used, and when SetPos is repeated, such records should be cleared and the state reset, so that the trial process will not be affected again. Basically, the control bar's self-defined hiding order is realized. We just need to configure the weight of each control in the xml file.

Achieving results:



Posted by heavenly on Tue, 26 Mar 2019 02:12:28 -0700