Skip to content

Commit 525a042

Browse files
authored
Implement container Card (o3de#11114)
It's a new style for card that is meant for "sections" in property editors for grouping things. To implement the underline in CardHeader a new internal widget was introduced, a simple single-colored Rectangle. It wasn't feasible to implement the underline with stylesheets because: * the color may change at runtime so it can't be explicitly set in the stylesheet * ::setStylesheet() would conflict with what's happening in Card::polish() Container card header styling got partially decoupled from the Card itself because CardHeader will be later used stand-alone (see o3de#10960). Fixes: o3de#10958 Signed-off-by: Miłosz Kosobucki <milosz@kosobucki.pl>
1 parent 5889b75 commit 525a042

10 files changed

Lines changed: 183 additions & 3 deletions

File tree

Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Card.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525

2626
namespace AzQtComponents
2727
{
28+
29+
static QString g_containerCardClass = QStringLiteral("ContainerCard");
30+
2831
static QPixmap ApplyAlphaToPixmap(const QPixmap& pixmap, float alpha)
2932
{
3033
QImage image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
@@ -40,6 +43,12 @@ namespace AzQtComponents
4043
return QPixmap::fromImage(image);
4144
}
4245

46+
void Card::applyContainerStyle(Card* card)
47+
{
48+
Style::addClass(card, g_containerCardClass);
49+
CardHeader::applyContainerStyle(card->header());
50+
}
51+
4352
Card::Card(QWidget* parent /* = nullptr */)
4453
// DO NOT SET THE PARENT OF THE CardHeader TO THIS!
4554
// It will cause a crash, because the this object is not initialized enough to be used as a parent object.

Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Card.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ namespace AzQtComponents
5959
qreal disabledIconAlpha; //!< Alpha value for disabled icons. Must be a value between 0.0 and 1.0.
6060
};
6161

62+
static void applyContainerStyle(Card* card);
63+
6264
Card(QWidget* parent = nullptr);
6365

6466
//! Sets the Primary Content Widget for this Card.

Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Card.qss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,25 @@ AzQtComponents--Card[selected="true"][expanded="false"] > AzQtComponents--CardHe
112112
border-radius: 2px
113113
}
114114

115+
AzQtComponents--Card[class~="ContainerCard"]
116+
{
117+
border: none;
118+
border-image: none;
119+
}
120+
121+
AzQtComponents--Card[class~="ContainerCard"] > #contentContainer
122+
{
123+
background-color: transparent;
124+
border: none;
125+
}
126+
127+
AzQtComponents--CardHeader[class~="ContainerCardHeader"]
128+
{
129+
background-color: #333333;
130+
border-radius: 0px;
131+
border: none;
132+
}
133+
115134
.primaryCardHeader
116135
{
117136
background-color: #333333;

Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/CardHeader.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <AzQtComponents/Components/Widgets/CardHeader.h>
1010
#include <AzQtComponents/Components/Widgets/CheckBox.h>
11+
#include <AzQtComponents/Components/Widgets/Internal/RectangleWidget.h>
1112
#include <AzQtComponents/Components/Style.h>
1213
#include <AzQtComponents/Components/StyleHelpers.h>
1314

@@ -23,6 +24,8 @@
2324

2425
namespace AzQtComponents
2526
{
27+
static QString g_containerCardHeaderClass = QStringLiteral("ContainerCardHeader");
28+
2629
namespace HeaderBarConstants
2730
{
2831
// names for widgets so they can be found in stylesheet
@@ -34,9 +37,15 @@ namespace AzQtComponents
3437
static const char* kContextMenuId = "ContextMenu";
3538
static const char* kContextMenuPlusIconId = "ContextMenuPlusIcon";
3639
static const char* khelpButtonId = "Help";
40+
static const char* kUnderlineRectId = "UnderlineRectangle";
3741

3842
static const char* kCardHeaderIconClassName = "CardHeaderIcon";
3943
static const char* kCardHeaderMenuClassName = "CardHeaderMenu";
44+
} // namespace HeaderBarConstants
45+
46+
void CardHeader::applyContainerStyle(CardHeader* header)
47+
{
48+
Style::addClass(header, g_containerCardHeaderClass);
4049
}
4150

4251
int CardHeader::s_iconSize = CardHeader::defaultIconSize();
@@ -112,6 +121,12 @@ namespace AzQtComponents
112121
m_mainLayout->setContentsMargins(0, 0, 0, 0);
113122
m_mainLayout->addWidget(m_backgroundFrame);
114123

124+
m_underlineWidget = new Internal::RectangleWidget(this);
125+
m_underlineWidget->setObjectName(HeaderBarConstants::kUnderlineRectId);
126+
m_underlineWidget->setFixedHeight(2);
127+
setUnderlineColor(QColor());
128+
m_mainLayout->addWidget(m_underlineWidget);
129+
115130
StyleHelpers::repolishWhenPropertyChanges(this, &CardHeader::warningChanged);
116131
StyleHelpers::repolishWhenPropertyChanges(this, &CardHeader::readOnlyChanged);
117132
StyleHelpers::repolishWhenPropertyChanges(this, &CardHeader::contentModifiedChanged);
@@ -140,7 +155,7 @@ namespace AzQtComponents
140155
m_titleLabel->update();
141156
}
142157

143-
void CardHeader::setTitleProperty(const char *name, const QVariant &value)
158+
void CardHeader::setTitleProperty(const char* name, const QVariant& value)
144159
{
145160
m_titleLabel->setProperty(name, value);
146161
}
@@ -179,7 +194,7 @@ namespace AzQtComponents
179194
m_warningLabel->setPixmap(m_warningIcon.pixmap(s_iconSize, s_iconSize));
180195
}
181196
}
182-
197+
183198
void CardHeader::mockDisabledState(bool disabled)
184199
{
185200
m_iconLabel->setDisabled(disabled);
@@ -273,7 +288,7 @@ namespace AzQtComponents
273288

274289
void CardHeader::mouseDoubleClickEvent(QMouseEvent* event)
275290
{
276-
//allow double click to expand/contract
291+
// allow double click to expand/contract
277292
if (event->button() == Qt::LeftButton && isExpandable())
278293
{
279294
bool expand = !isExpanded();
@@ -356,6 +371,14 @@ namespace AzQtComponents
356371
Style::addClass(m_contextMenuButton, HeaderBarConstants::kCardHeaderMenuClassName);
357372
}
358373

374+
void CardHeader::setUnderlineColor(const QColor& color)
375+
{
376+
m_underlineWidget->setColor(color);
377+
378+
const bool underlineVisible = color.isValid() && color.alpha() != 0;
379+
m_underlineWidget->setVisible(underlineVisible);
380+
}
381+
359382
} // namespace AzQtComponents
360383

361384
#include "Components/Widgets/moc_CardHeader.cpp"

Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/CardHeader.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ class QLabel;
2424

2525
namespace AzQtComponents
2626
{
27+
namespace Internal
28+
{
29+
class RectangleWidget;
30+
}
31+
2732
//! Header bar for Card widgets.
2833
//! Provides a bar with an expander arrow, a text title and a button to trigger a context menu.
2934
//! Also has an optional icon and help button.
@@ -47,6 +52,8 @@ namespace AzQtComponents
4752
Plus //!< Plus button, usually tied to add actions.
4853
};
4954

55+
static void applyContainerStyle(CardHeader* header);
56+
5057
CardHeader(QWidget* parent = nullptr);
5158

5259
//! Sets the Card Header title. Passing an empty string will hide the Card Header.
@@ -129,6 +136,10 @@ namespace AzQtComponents
129136
//! Sets the icon to be displayed for the context menu.
130137
void setContextMenuIcon(ContextMenuIcon iconType);
131138

139+
//! Sets the small solid color underline under the header. If color is
140+
//! invalid or transparent, the underline will disappear completely.
141+
void setUnderlineColor(const QColor& color);
142+
132143
Q_SIGNALS:
133144
//! Triggered when the context menu button is clicked, or on a right click.
134145
void contextMenuRequested(const QPoint& position);
@@ -162,6 +173,7 @@ namespace AzQtComponents
162173
QLabel* m_warningLabel = nullptr;
163174
QPushButton* m_contextMenuButton = nullptr;
164175
QPushButton* m_helpButton = nullptr;
176+
Internal::RectangleWidget* m_underlineWidget = nullptr;
165177
bool m_warning = false;
166178
bool m_readOnly = false;
167179
bool m_modified = false;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) Contributors to the Open 3D Engine Project.
3+
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
4+
*
5+
* SPDX-License-Identifier: Apache-2.0 OR MIT
6+
*
7+
*/
8+
9+
#include <AzQtComponents/Components/Widgets/Internal/RectangleWidget.h>
10+
11+
#include <QPaintEvent>
12+
#include <QPainter>
13+
#include <QWidget>
14+
15+
namespace AzQtComponents::Internal
16+
{
17+
RectangleWidget::RectangleWidget(QWidget* parent)
18+
: QWidget(parent)
19+
{
20+
}
21+
22+
void RectangleWidget::setColor(const QColor& color)
23+
{
24+
if (color != m_color)
25+
{
26+
m_color = color;
27+
update();
28+
}
29+
}
30+
31+
QColor RectangleWidget::color() const
32+
{
33+
return m_color;
34+
}
35+
36+
void RectangleWidget::paintEvent(QPaintEvent* event)
37+
{
38+
QPainter p(this);
39+
p.fillRect(event->rect(), m_color);
40+
41+
QWidget::paintEvent(event);
42+
}
43+
} // namespace AzQtComponents::Internal
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (c) Contributors to the Open 3D Engine Project.
3+
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
4+
*
5+
* SPDX-License-Identifier: Apache-2.0 OR MIT
6+
*
7+
*/
8+
#pragma once
9+
10+
#if !defined(Q_MOC_RUN)
11+
#include <AzQtComponents/AzQtComponentsAPI.h>
12+
13+
#include <QWidget>
14+
#endif
15+
16+
class QPaintEvent;
17+
18+
namespace AzQtComponents::Internal
19+
{
20+
//! A rectangle of a single color.
21+
//! To be used when filling some space with a solid color
22+
//! is not feasible through styling.
23+
class AZ_QT_COMPONENTS_API RectangleWidget : public QWidget
24+
{
25+
Q_OBJECT
26+
public:
27+
explicit RectangleWidget(QWidget* parent);
28+
29+
//! Set the color with which the rectangle will be painted.
30+
void setColor(const QColor& color);
31+
//! Get the current color of the rectangle.
32+
[[nodiscard]] QColor color() const;
33+
34+
protected:
35+
void paintEvent(QPaintEvent* event) override;
36+
37+
private:
38+
QColor m_color = Qt::white;
39+
};
40+
41+
} // namespace AzQtComponents::Internal

Code/Framework/AzQtComponents/AzQtComponents/Gallery/CardPage.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ header->setHelpURL("https://o3de.org/docs/");
7070
// Clear the help url
7171
header->clearHelpURL();
7272
73+
// Set header underline color to make some card visually popping
74+
header->setUnderlineColor(Qt::cyan);
75+
76+
// Container cards don't have a visually distinct contents area and can
77+
// be used for grouping other cards and widgets
78+
AzQtComponents* containerCard;
79+
AzQtComponents::Card::applyContainerStyle(containerCard);
80+
auto* nestedCard = new AzQtComponents::Card();
81+
containerCard->setContentWidget(nestedCard);
82+
7383
// Populate the menu that pops up when the header bar is right clicked or when the user clicks on the menu button
7484
connect(card, &AzQtComponents::Card::contextMenuRequested, this, [](const QPoint& pos){
7585
QMenu menu;
@@ -120,6 +130,22 @@ card->mockDisabledState(true);
120130
ui->functionalCard->setTitle("Card With Secondary Section");
121131
addContentWidget(ui->functionalCard, 60);
122132

133+
AzQtComponents::Card::applyContainerStyle(ui->containerCard);
134+
ui->containerCard->setTitle("Container card");
135+
ui->containerCard->header()->setUnderlineColor(QColor("#a675ff"));
136+
137+
auto* contentWidget = new QWidget;
138+
auto* nestedLayout = new QVBoxLayout(contentWidget);
139+
ui->containerCard->setContentWidget(contentWidget);
140+
141+
auto* nestedCard = new AzQtComponents::Card();
142+
nestedCard->setTitle("Nested card");
143+
addContentWidget(nestedCard, 30);
144+
nestedLayout->addWidget(nestedCard);
145+
146+
auto* button = new QPushButton("Nested button");
147+
nestedLayout->addWidget(button);
148+
123149
// put in an example icon
124150
AzQtComponents::CardHeader* header = ui->functionalCard->header();
125151
header->setIcon(QIcon(":/Cards/img/UI20/Cards/slice_item.png"));

Code/Framework/AzQtComponents/AzQtComponents/Gallery/CardPage.ui

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
<item>
7676
<widget class="AzQtComponents::Card" name="functionalCard" native="true"/>
7777
</item>
78+
<item>
79+
<widget class="AzQtComponents::Card" name="containerCard" native="true"/>
80+
</item>
7881
<item>
7982
<spacer name="verticalSpacer_2">
8083
<property name="orientation">

Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_files.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ set(FILES
171171
Components/Widgets/MessageBox.h
172172
Components/Widgets/OverlayWidget.cpp
173173
Components/Widgets/OverlayWidget.h
174+
Components/Widgets/Internal/RectangleWidget.cpp
175+
Components/Widgets/Internal/RectangleWidget.h
174176
Components/Widgets/Internal/OverlayWidgetLayer.cpp
175177
Components/Widgets/Internal/OverlayWidgetLayer.h
176178
Components/Widgets/Internal/OverlayWidgetLayer.ui

0 commit comments

Comments
 (0)