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;
}