winrt::UIElement BuildActionSetHelper()

in source/uwp/Renderer/lib/ActionHelpers.cpp [783:959]


    winrt::UIElement BuildActionSetHelper(winrt::AdaptiveCard const& adaptiveCard,
                                          winrt::AdaptiveActionSet const& adaptiveActionSet,
                                          winrt::IVector<winrt::IAdaptiveActionElement> const& children,
                                          winrt::AdaptiveRenderContext const& renderContext,
                                          winrt::AdaptiveRenderArgs const& renderArgs)
    {
        auto hostConfig = renderContext.HostConfig();
        auto actionsConfig = hostConfig.Actions();

        winrt::ActionAlignment actionAlignment = actionsConfig.ActionAlignment();
        winrt::ActionsOrientation actionsOrientation = actionsConfig.ActionsOrientation();

        // Declare the panel that will host the buttons
        winrt::Panel actionsPanel{nullptr};
        winrt::IVector<winrt::ColumnDefinition> columnDefinitions;

        if (actionAlignment == winrt::ActionAlignment::Stretch && actionsOrientation == winrt::ActionsOrientation::Horizontal)
        {
            // If stretch alignment and orientation is horizontal, we use a grid with equal column widths to achieve
            // stretch behavior. For vertical orientation, we'll still just use a stack panel since the concept of
            // stretching buttons height isn't really valid, especially when the height of cards are typically dynamic.
            winrt::Grid actionsGrid;
            columnDefinitions = actionsGrid.ColumnDefinitions();
            actionsPanel = actionsGrid;
        }
        else
        {
            // Create a stack panel for the action buttons
            winrt::StackPanel actionStackPanel{};

            const auto uiOrientation = (actionsOrientation == winrt::ActionsOrientation::Horizontal) ?
                winrt::Orientation::Horizontal :
                winrt::Orientation::Vertical;

            actionStackPanel.Orientation(uiOrientation);

            switch (actionAlignment)
            {
            case winrt::ActionAlignment::Center:
                actionStackPanel.HorizontalAlignment(winrt::HorizontalAlignment::Center);
                break;
            case winrt::ActionAlignment::Left:
                actionStackPanel.HorizontalAlignment(winrt::HorizontalAlignment::Left);
                break;
            case winrt::ActionAlignment::Right:
                actionStackPanel.HorizontalAlignment(winrt::HorizontalAlignment::Right);
                break;
            case winrt::ActionAlignment::Stretch:
                actionStackPanel.HorizontalAlignment(winrt::HorizontalAlignment::Stretch);
                break;
            }

            // Add the action buttons to the stack panel
            actionsPanel = actionStackPanel;
        }

        auto buttonMargin = GetButtonMargin(actionsConfig);
        if (actionsOrientation == winrt::ActionsOrientation::Horizontal)
        {
            // Negate the spacing on the sides so the left and right buttons are flush on the side.
            // We do NOT remove the margin from the individual button itself, since that would cause
            // the equal columns stretch behavior to not have equal columns (since the first and last
            // button would be narrower without the same margins as its peers).
            actionsPanel.Margin({-buttonMargin.Left, 0, -buttonMargin.Right, 0});
        }
        else
        {
            // Negate the spacing on the top and bottom so the first and last buttons don't have extra padding
            actionsPanel.Margin({0, -buttonMargin.Top, 0, -buttonMargin.Bottom});
        }

        // Get the max number of actions and check the host config to confirm whether we render actions beyond the max in the overflow menu
        auto maxActions = actionsConfig.MaxActions();

        auto hostConfigImpl = peek_innards<winrt::implementation::AdaptiveHostConfig>(hostConfig);
        bool overflowMaxActions = hostConfigImpl->OverflowMaxActions();

        winrt::StackPanel showCardsStackPanel{};
        winrt::Button overflowButton{nullptr};
        uint32_t currentButtonIndex{0};

        for (auto action : children)
        {
            auto mode = action.Mode();
            winrt::UIElement actionControl{nullptr};

            if (action.IconUrl().empty())
            {
                renderArgs.AllowAboveTitleIconPlacement(false);
            }

            if (currentButtonIndex < maxActions && mode == winrt::AdaptiveCards::ObjectModel::Uwp::ActionMode::Primary)
            {
                // If we have fewer than the maximum number of actions and this action's mode is primary, make a button
                actionControl = CreateActionButtonInActionSet(adaptiveCard,
                                                              adaptiveActionSet,
                                                              action,
                                                              currentButtonIndex,
                                                              actionsPanel,
                                                              showCardsStackPanel,
                                                              columnDefinitions,
                                                              renderContext,
                                                              renderArgs);

                currentButtonIndex++;
            }
            else if (currentButtonIndex >= maxActions &&
                     (mode == winrt::AdaptiveCards::ObjectModel::Uwp::ActionMode::Primary) && !overflowMaxActions)
            {
                // If we have more primary actions than the max actions and we're not allowed to overflow them just set a warning and continue
                renderContext.AddWarning(winrt::WarningStatusCode::MaxActionsExceeded,
                                         {L"Some actions were not rendered due to exceeding the maximum number of actions allowed"});
                return S_OK;
            }
            else
            {
                // If the action's mode is secondary or we're overflowing max actions, create a flyout item on the overflow menu
                if (overflowButton == nullptr)
                {
                    // Create a button for the overflow menu if it doesn't exist yet
                    overflowButton = CreateFlyoutButton(renderContext, renderArgs);
                }

                // Add a flyout item to the overflow menu
                AddOverflowFlyoutItem(action, overflowButton, adaptiveCard, adaptiveActionSet, showCardsStackPanel, renderContext, renderArgs);

                // If this was supposed to be a primary action but it got overflowed due to max actions, add a warning
                if (mode == winrt::AdaptiveCards::ObjectModel::Uwp::ActionMode::Primary)
                {
                    renderContext.AddWarning(winrt::WarningStatusCode::MaxActionsExceeded,
                                             {L"Some actions were moved to an overflow menu due to exceeding the maximum number of actions allowed"});
                }
            }
        }

        // Lastly add the overflow button itself to the action panel
        if (overflowButton)
        {
            // If using equal width columns, add another column and assign the it to the overflow button
            if (columnDefinitions)
            {
                winrt::ColumnDefinition columnDefinition{};
                columnDefinition.Width({1.0, winrt::GridUnitType::Star});
                columnDefinitions.Append(columnDefinition);
                winrt::Grid::SetColumn(overflowButton, currentButtonIndex);
            }

            // Add the overflow button to the panel
            XamlHelpers::AppendXamlElementToPanel(overflowButton, actionsPanel);

            // Register the overflow button with the render context

            if (const auto contextImpl = peek_innards<winrt::implementation::AdaptiveRenderContext>(renderContext))
            {
                if (adaptiveActionSet)
                {
                    contextImpl->AddOverflowButton(adaptiveActionSet, overflowButton);
                }
                else
                {
                    contextImpl->AddOverflowButton(adaptiveCard, overflowButton);
                }
            }
        }

        // Reset icon placement value
        renderArgs.AllowAboveTitleIconPlacement(false);
        XamlHelpers::SetStyleFromResourceDictionary(renderContext, {L"Adaptive.Actions"}, actionsPanel);

        winrt::StackPanel actionPanel;

        // Add buttons and show cards to panel
        XamlHelpers::AppendXamlElementToPanel(actionsPanel, actionPanel);
        XamlHelpers::AppendXamlElementToPanel(showCardsStackPanel, actionPanel);

        return actionPanel;
    }