diff options
author | Alessandro Portale <alessandro.portale@qt.io> | 2024-02-12 17:19:41 +0100 |
---|---|---|
committer | Alessandro Portale <alessandro.portale@qt.io> | 2024-02-27 16:42:10 +0000 |
commit | 903d01b93459d0f1ee70e6ca74a8c01af5e0981b (patch) | |
tree | 9a8991c6282fbf558a9acda51388e37f195bca72 | |
parent | e2a0dd2cefe5265a9d3ba59de766f73302cf0082 (diff) |
Welcome: Implement new design
2024 redesign
Change-Id: I6629849921272d856f201693973a8e29c6465e94
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
43 files changed, 1528 insertions, 1207 deletions
diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme index a7dcedd104..c2846fa094 100644 --- a/share/qtcreator/themes/dark.creatortheme +++ b/share/qtcreator/themes/dark.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=dark.figmatokens ThemeName=Dark PreferredStyles= DefaultTextEditorColorScheme=dark.xml @@ -406,39 +407,6 @@ Debugger_WatchItem_ValueChanged=ffff6666 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - dark mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff8f8f8 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ff1f1f1f -Token_Background_Muted=ff262626 -Token_Background_Subtle=ff2e2e2e -Token_Foreground_Default=ff5a5a5a -Token_Foreground_Muted=ff3e3e3e -Token_Foreground_Subtle=ff303030 -Token_Text_Default=fff8f8f8 -Token_Text_Muted=ffaeaeae -Token_Text_Subtle=ff595959 -Token_Stroke_Strong=ffeeeeee -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ff3a3a3a -Token_Notification_Alert=ffc98014 -Token_Notification_Success=ff1f9b5d -Token_Notification_Neutral=ff016876 -Token_Notification_Danger=ffb22245 - -Welcome_TextColor=text -Welcome_ForegroundPrimaryColor=ffa3a3a3 -Welcome_ForegroundSecondaryColor=ff808080 -Welcome_BackgroundPrimaryColor=normalBackground -Welcome_BackgroundSecondaryColor=shadowBackground -Welcome_HoverColor=ff404040 -Welcome_AccentColor=ff57d658 -Welcome_LinkColor=ff67e668 -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=text Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor2=ff444444 diff --git a/share/qtcreator/themes/dark.figmatokens b/share/qtcreator/themes/dark.figmatokens new file mode 100644 index 0000000000..fdd98e80c1 --- /dev/null +++ b/share/qtcreator/themes/dark.figmatokens @@ -0,0 +1,31 @@ +; Qt Creator Color Tokens - dark mode + +[Colors] + +Token_Basic_Black=ff131313 +Token_Basic_White=fff8f8f8 + +Token_Accent_Default=ff23b26a +Token_Accent_Muted=ff1f9b5d +Token_Accent_Subtle=ff1a8550 + +Token_Background_Default=ff1f1f1f +Token_Background_Muted=ff262626 +Token_Background_Subtle=ff2e2e2e + +Token_Foreground_Default=ff5a5a5a +Token_Foreground_Muted=ff3e3e3e +Token_Foreground_Subtle=ff303030 + +Token_Text_Default=fff8f8f8 +Token_Text_Muted=ffaeaeae +Token_Text_Subtle=ff595959 + +Token_Stroke_Strong=ffeeeeee +Token_Stroke_Muted=ff727272 +Token_Stroke_Subtle=ff3a3a3a + +Token_Notification_Alert=ffc98014 +Token_Notification_Success=ff1f9b5d +Token_Notification_Neutral=ff016876 +Token_Notification_Danger=ffb22245 diff --git a/share/qtcreator/themes/default.creatortheme b/share/qtcreator/themes/default.creatortheme index 2dee7ab246..f2c4c7f7a4 100644 --- a/share/qtcreator/themes/default.creatortheme +++ b/share/qtcreator/themes/default.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=light.figmatokens ThemeName=Classic PreferredStyles= @@ -398,39 +399,6 @@ Debugger_WatchItem_ValueChanged=ffc80000 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - light mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff2f2f2 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ffe3e3e3 -Token_Background_Muted=ffeeeeee -Token_Background_Subtle=fffbfbfb -Token_Foreground_Default=ffcdcdcd -Token_Foreground_Muted=ffd5d5d5 -Token_Foreground_Subtle=ffdddddd -Token_Text_Default=ff393939 -Token_Text_Muted=ff7c7c7c -Token_Text_Subtle=ffbebebe -Token_Stroke_Strong=ff464646 -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ffcdcdcd -Token_Notification_Alert=ffeb991f -Token_Notification_Success=ff23b26a -Token_Notification_Neutral=ff0e7887 -Token_Notification_Danger=ffdc1343 - -Welcome_TextColor=ff000000 -Welcome_ForegroundPrimaryColor=shadowBackground -Welcome_ForegroundSecondaryColor=ff939393 -Welcome_BackgroundPrimaryColor=fffafafa -Welcome_BackgroundSecondaryColor=ffffffff -Welcome_HoverColor=ffefefef -Welcome_AccentColor=ff45ce55 -Welcome_LinkColor=ff20a020 -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=darkText Timeline_BackgroundColor1=ffffffff Timeline_BackgroundColor2=fff6f6f6 diff --git a/share/qtcreator/themes/design-light.creatortheme b/share/qtcreator/themes/design-light.creatortheme index 8931b62042..41a9f07ba8 100644 --- a/share/qtcreator/themes/design-light.creatortheme +++ b/share/qtcreator/themes/design-light.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=light.figmatokens ThemeName=Design Light PreferredStyles= @@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - light mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff2f2f2 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ffe3e3e3 -Token_Background_Muted=ffeeeeee -Token_Background_Subtle=fffbfbfb -Token_Foreground_Default=ffcdcdcd -Token_Foreground_Muted=ffd5d5d5 -Token_Foreground_Subtle=ffdddddd -Token_Text_Default=ff393939 -Token_Text_Muted=ff7c7c7c -Token_Text_Subtle=ffbebebe -Token_Stroke_Strong=ff464646 -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ffcdcdcd -Token_Notification_Alert=ffeb991f -Token_Notification_Success=ff23b26a -Token_Notification_Neutral=ff0e7887 -Token_Notification_Danger=ffdc1343 - -Welcome_TextColor=ff000000 -Welcome_ForegroundPrimaryColor=ff404040 -Welcome_ForegroundSecondaryColor=ff727272 -Welcome_BackgroundPrimaryColor=ffeaeaea -Welcome_BackgroundSecondaryColor=ffefefef -Welcome_HoverColor=ffe1e1e1 -Welcome_AccentColor=ff25709a -Welcome_LinkColor=ff104090 -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=text Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor2=fff6f6f6 diff --git a/share/qtcreator/themes/design.creatortheme b/share/qtcreator/themes/design.creatortheme index e29240a3cf..11022bd7e5 100644 --- a/share/qtcreator/themes/design.creatortheme +++ b/share/qtcreator/themes/design.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=dark.figmatokens ThemeName=Design Dark PreferredStyles= DefaultTextEditorColorScheme=creator-dark.xml @@ -414,39 +415,6 @@ Debugger_WatchItem_ValueChanged=ffff6666 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - dark mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff8f8f8 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ff1f1f1f -Token_Background_Muted=ff262626 -Token_Background_Subtle=ff2e2e2e -Token_Foreground_Default=ff5a5a5a -Token_Foreground_Muted=ff3e3e3e -Token_Foreground_Subtle=ff303030 -Token_Text_Default=fff8f8f8 -Token_Text_Muted=ffaeaeae -Token_Text_Subtle=ff595959 -Token_Stroke_Strong=ffeeeeee -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ff3a3a3a -Token_Notification_Alert=ffc98014 -Token_Notification_Success=ff1f9b5d -Token_Notification_Neutral=ff016876 -Token_Notification_Danger=ffb22245 - -Welcome_TextColor=text -Welcome_ForegroundPrimaryColor=ffa3a3a3 -Welcome_ForegroundSecondaryColor=ff808080 -Welcome_BackgroundPrimaryColor=ff242424 -Welcome_BackgroundSecondaryColor=ff1c1c1c -Welcome_HoverColor=ff2b2a2a -Welcome_AccentColor=ff3f8ccc -Welcome_LinkColor=ff5fafef -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=text Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor2=ff444444 diff --git a/share/qtcreator/themes/flat-dark.creatortheme b/share/qtcreator/themes/flat-dark.creatortheme index 7e94645b74..9737e9cd53 100644 --- a/share/qtcreator/themes/flat-dark.creatortheme +++ b/share/qtcreator/themes/flat-dark.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=dark.figmatokens ThemeName=Flat Dark PreferredStyles= DefaultTextEditorColorScheme=creator-dark.xml @@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffff6666 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - dark mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff8f8f8 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ff1f1f1f -Token_Background_Muted=ff262626 -Token_Background_Subtle=ff2e2e2e -Token_Foreground_Default=ff5a5a5a -Token_Foreground_Muted=ff3e3e3e -Token_Foreground_Subtle=ff303030 -Token_Text_Default=fff8f8f8 -Token_Text_Muted=ffaeaeae -Token_Text_Subtle=ff595959 -Token_Stroke_Strong=ffeeeeee -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ff3a3a3a -Token_Notification_Alert=ffc98014 -Token_Notification_Success=ff1f9b5d -Token_Notification_Neutral=ff016876 -Token_Notification_Danger=ffb22245 - -Welcome_TextColor=text -Welcome_ForegroundPrimaryColor=ff999999 -Welcome_ForegroundSecondaryColor=ff808080 -Welcome_BackgroundPrimaryColor=normalBackground -Welcome_BackgroundSecondaryColor=ff242628 -Welcome_HoverColor=ff404243 -Welcome_AccentColor=ff36c148 -Welcome_LinkColor=ff5fcf4f -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=text Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor2=ff444444 diff --git a/share/qtcreator/themes/flat-light.creatortheme b/share/qtcreator/themes/flat-light.creatortheme index eddf38971d..1ff8a7a3ff 100644 --- a/share/qtcreator/themes/flat-light.creatortheme +++ b/share/qtcreator/themes/flat-light.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=light.figmatokens ThemeName=Flat Light PreferredStyles= @@ -407,39 +408,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - light mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff2f2f2 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ffe3e3e3 -Token_Background_Muted=ffeeeeee -Token_Background_Subtle=fffbfbfb -Token_Foreground_Default=ffcdcdcd -Token_Foreground_Muted=ffd5d5d5 -Token_Foreground_Subtle=ffdddddd -Token_Text_Default=ff393939 -Token_Text_Muted=ff7c7c7c -Token_Text_Subtle=ffbebebe -Token_Stroke_Strong=ff464646 -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ffcdcdcd -Token_Notification_Alert=ffeb991f -Token_Notification_Success=ff23b26a -Token_Notification_Neutral=ff0e7887 -Token_Notification_Danger=ffdc1343 - -Welcome_TextColor=ff000000 -Welcome_ForegroundPrimaryColor=ff232323 -Welcome_ForegroundSecondaryColor=ff939393 -Welcome_BackgroundPrimaryColor=fffafafa -Welcome_BackgroundSecondaryColor=ffffffff -Welcome_HoverColor=ffefefef -Welcome_AccentColor=ff45ce55 -Welcome_LinkColor=ff20a020 -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=text Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor2=fff6f6f6 diff --git a/share/qtcreator/themes/flat.creatortheme b/share/qtcreator/themes/flat.creatortheme index 08697334f4..f479077889 100644 --- a/share/qtcreator/themes/flat.creatortheme +++ b/share/qtcreator/themes/flat.creatortheme @@ -1,4 +1,5 @@ [General] +Includes=light.figmatokens ThemeName=Flat PreferredStyles= @@ -405,39 +406,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303 Debugger_Breakpoint_TextMarkColor=ffff4040 -; Qt Creator Color Tokens - light mode -Token_Basic_Black=ff131313 -Token_Basic_White=fff2f2f2 -Token_Accent_Default=ff23b26a -Token_Accent_Muted=ff1f9b5d -Token_Accent_Subtle=ff1a8550 -Token_Background_Default=ffe3e3e3 -Token_Background_Muted=ffeeeeee -Token_Background_Subtle=fffbfbfb -Token_Foreground_Default=ffcdcdcd -Token_Foreground_Muted=ffd5d5d5 -Token_Foreground_Subtle=ffdddddd -Token_Text_Default=ff393939 -Token_Text_Muted=ff7c7c7c -Token_Text_Subtle=ffbebebe -Token_Stroke_Strong=ff464646 -Token_Stroke_Muted=ff727272 -Token_Stroke_Subtle=ffcdcdcd -Token_Notification_Alert=ffeb991f -Token_Notification_Success=ff23b26a -Token_Notification_Neutral=ff0e7887 -Token_Notification_Danger=ffdc1343 - -Welcome_TextColor=ff000000 -Welcome_ForegroundPrimaryColor=shadowBackground -Welcome_ForegroundSecondaryColor=ff939393 -Welcome_BackgroundPrimaryColor=fffafafa -Welcome_BackgroundSecondaryColor=ffffffff -Welcome_HoverColor=ffefefef -Welcome_AccentColor=ff45ce55 -Welcome_LinkColor=ff20a020 -Welcome_DisabledLinkColor=textDisabled - Timeline_TextColor=text Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor2=fff6f6f6 diff --git a/share/qtcreator/themes/light.figmatokens b/share/qtcreator/themes/light.figmatokens new file mode 100644 index 0000000000..a4d1bfcaa8 --- /dev/null +++ b/share/qtcreator/themes/light.figmatokens @@ -0,0 +1,31 @@ +; Qt Creator Color Tokens - light mode + +[Colors] + +Token_Basic_Black=ff131313 +Token_Basic_White=fff2f2f2 + +Token_Accent_Default=ff23b26a +Token_Accent_Muted=ff1f9b5d +Token_Accent_Subtle=ff1a8550 + +Token_Background_Default=fffcfcfc +Token_Background_Muted=ffefefef +Token_Background_Subtle=ffe7e7e7 + +Token_Foreground_Default=ffcdcdcd +Token_Foreground_Muted=ffd5d5d5 +Token_Foreground_Subtle=ffdddddd + +Token_Text_Default=ff393939 +Token_Text_Muted=ff6a6a6a +Token_Text_Subtle=ffbebebe + +Token_Stroke_Strong=ff464646 +Token_Stroke_Muted=ff727272 +Token_Stroke_Subtle=ffcdcdcd + +Token_Notification_Alert=ffeb991f +Token_Notification_Success=ff23b26a +Token_Notification_Neutral=ff0e7887 +Token_Notification_Danger=ffdc1343 diff --git a/src/libs/utils/stylehelper.cpp b/src/libs/utils/stylehelper.cpp index ffb7efc417..991b43df52 100644 --- a/src/libs/utils/stylehelper.cpp +++ b/src/libs/utils/stylehelper.cpp @@ -933,7 +933,17 @@ QColor StyleHelper::ensureReadableOn(const QColor &background, const QColor &des return foreground; } -static QStringList brandFontFamilies() +static const QStringList &applicationFontFamilies() +{ + const static QStringList families = [] { + constexpr QLatin1String familyName("Inter"); + // Font is either installed in the system, or was loaded from share/qtcreator/fonts/ + return QFontDatabase::hasFamily(familyName) ? QStringList(familyName) : QStringList(); + }(); + return families; +} + +static const QStringList &brandFontFamilies() { const static QStringList families = []{ const int id = QFontDatabase::addApplicationFont(":/studiofonts/TitilliumWeb-Regular.ttf"); @@ -959,10 +969,14 @@ static const UiFontMetrics& uiFontMetrics(StyleHelper::UiElement element) {StyleHelper::UiElementH5, {14, 16, QFont::DemiBold}}, {StyleHelper::UiElementH6, {12, 14, QFont::DemiBold}}, {StyleHelper::UiElementH6Capital, {12, 14, QFont::DemiBold}}, + {StyleHelper::UiElementBody1, {14, 20, QFont::Light}}, + {StyleHelper::UiElementBody2, {12, 20, QFont::Light}}, + {StyleHelper::UiElementButtonMedium, {12, 16, QFont::Bold}}, + {StyleHelper::UiElementButtonSmall, {10, 12, QFont::Bold}}, {StyleHelper::UiElementCaptionStrong, {10, 12, QFont::DemiBold}}, {StyleHelper::UiElementCaption, {10, 12, QFont::Normal}}, - {StyleHelper::UIElementIconStandard, {12, 16, QFont::Normal}}, - {StyleHelper::UIElementIconActive, {12, 16, QFont::DemiBold}}, + {StyleHelper::UiElementIconStandard, {12, 16, QFont::Medium}}, + {StyleHelper::UiElementIconActive, {12, 16, QFont::DemiBold}}, }; QTC_ASSERT(metrics.count(element) > 0, return metrics.at(StyleHelper::UiElementCaptionStrong)); return metrics.at(element); @@ -983,8 +997,10 @@ QFont StyleHelper::uiFont(UiElement element) case UiElementH3: case UiElementH6Capital: font.setCapitalization(QFont::AllUppercase); - break; + [[fallthrough]]; default: + if (!applicationFontFamilies().isEmpty()) + font.setFamilies(applicationFontFamilies()); break; } diff --git a/src/libs/utils/stylehelper.h b/src/libs/utils/stylehelper.h index 4b986c6831..7d434925e6 100644 --- a/src/libs/utils/stylehelper.h +++ b/src/libs/utils/stylehelper.h @@ -42,15 +42,15 @@ constexpr char C_TOOLBAR_ACTIONWIDGET[] = "toolbar_actionWidget"; constexpr char C_QT_SCALE_FACTOR_ROUNDING_POLICY[] = "QT_SCALE_FACTOR_ROUNDING_POLICY"; namespace SpacingTokens { - constexpr int VPaddingXXS = 4; // Top and bottom padding within the component - constexpr int HPaddingXXS = 4; // Left and right padding within the component - constexpr int VGapXXS = 4; // Vertical Space between TEXT LINE within the Component - constexpr int HGapXXS = 4; // Horizontal Space between elements within the Component + constexpr int VPaddingXxs = 4; // Top and bottom padding within the component + constexpr int HPaddingXxs = 4; // Left and right padding within the component + constexpr int VGapXxs = 4; // Vertical Space between TEXT LINE within the Component + constexpr int HGapXxs = 4; // Horizontal Space between elements within the Component - constexpr int VPaddingXS = 8; - constexpr int HPaddingXS = 8; - constexpr int VGapXS = 4; - constexpr int HGapXS = 8; + constexpr int VPaddingXs = 8; + constexpr int HPaddingXs = 8; + constexpr int VGapXs = 4; + constexpr int HGapXs = 8; constexpr int VPaddingS = 8; constexpr int HPaddingS = 16; @@ -62,10 +62,15 @@ namespace SpacingTokens { constexpr int VGapM = 4; constexpr int HGapM = 16; - constexpr int VPaddingL = 12; + constexpr int VPaddingL = 16; constexpr int HPaddingL = 24; constexpr int VGapL = 8; constexpr int HGapL = 16; + + constexpr int ExPaddingGapS = 2; + constexpr int ExPaddingGapM = 6; + constexpr int ExPaddingGapL = 12; + constexpr int ExVPaddingGapXl = 24; } enum ToolbarStyle { @@ -82,10 +87,14 @@ enum UiElement { UiElementH5, UiElementH6, UiElementH6Capital, + UiElementBody1, + UiElementBody2, + UiElementButtonMedium, + UiElementButtonSmall, UiElementCaptionStrong, UiElementCaption, - UIElementIconStandard, - UIElementIconActive, + UiElementIconStandard, + UiElementIconActive, }; // Height of the project explorer navigation bar diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h index 2e4d61d943..65d0ee91ea 100644 --- a/src/libs/utils/theme/theme.h +++ b/src/libs/utils/theme/theme.h @@ -247,18 +247,6 @@ public: Token_Notification_Neutral, Token_Notification_Danger, - /* Welcome Plugin */ - - Welcome_TextColor, - Welcome_ForegroundPrimaryColor, - Welcome_ForegroundSecondaryColor, - Welcome_BackgroundPrimaryColor, - Welcome_BackgroundSecondaryColor, - Welcome_HoverColor, - Welcome_AccentColor, - Welcome_LinkColor, - Welcome_DisabledLinkColor, - /* Timeline Library */ Timeline_TextColor, Timeline_BackgroundColor1, diff --git a/src/plugins/coreplugin/core.qrc b/src/plugins/coreplugin/core.qrc index ff855e3f4b..239bd94612 100644 --- a/src/plugins/coreplugin/core.qrc +++ b/src/plugins/coreplugin/core.qrc @@ -1,5 +1,9 @@ <RCC> <qresource prefix="/core"> + <file>images/expandarrow.png</file> + <file>images/expandarrow@2x.png</file> + <file>images/search.png</file> + <file>images/search@2x.png</file> <file>images/settingscategory_core.png</file> <file>images/settingscategory_core@2x.png</file> <file>images/settingscategory_design.png</file> diff --git a/src/plugins/coreplugin/images/expandarrow.png b/src/plugins/coreplugin/images/expandarrow.png Binary files differnew file mode 100644 index 0000000000..c463d0236d --- /dev/null +++ b/src/plugins/coreplugin/images/expandarrow.png diff --git a/src/plugins/coreplugin/images/expandarrow@2x.png b/src/plugins/coreplugin/images/expandarrow@2x.png Binary files differnew file mode 100644 index 0000000000..9b8341673f --- /dev/null +++ b/src/plugins/coreplugin/images/expandarrow@2x.png diff --git a/src/plugins/coreplugin/images/search.png b/src/plugins/coreplugin/images/search.png Binary files differnew file mode 100644 index 0000000000..398e2fe2bb --- /dev/null +++ b/src/plugins/coreplugin/images/search.png diff --git a/src/plugins/coreplugin/images/search@2x.png b/src/plugins/coreplugin/images/search@2x.png Binary files differnew file mode 100644 index 0000000000..aaae492449 --- /dev/null +++ b/src/plugins/coreplugin/images/search@2x.png diff --git a/src/plugins/coreplugin/iwelcomepage.cpp b/src/plugins/coreplugin/iwelcomepage.cpp index 3eb63d8031..beef33f40d 100644 --- a/src/plugins/coreplugin/iwelcomepage.cpp +++ b/src/plugins/coreplugin/iwelcomepage.cpp @@ -3,25 +3,8 @@ #include "iwelcomepage.h" -#include <utils/icon.h> -#include <utils/theme/theme.h> -#include <utils/stylehelper.h> - -#include <QHBoxLayout> -#include <QLabel> -#include <QPainter> -#include <QPainterPath> -#include <QPixmap> -#include <QUrl> - -#include <qdrawutil.h> - -using namespace Utils; - namespace Core { -const char WITHACCENTCOLOR_PROPERTY_NAME[] = "_withAccentColor"; - static QList<IWelcomePage *> g_welcomePages; const QList<IWelcomePage *> IWelcomePage::allWelcomePages() @@ -39,171 +22,4 @@ IWelcomePage::~IWelcomePage() g_welcomePages.removeOne(this); } -QPalette WelcomePageFrame::buttonPalette(bool isActive, bool isCursorInside, bool forText) -{ - QPalette pal; - pal.setBrush(QPalette::Window, {}); - pal.setBrush(QPalette::WindowText, {}); - - Theme *theme = Utils::creatorTheme(); - if (isActive) { - if (forText) { - pal.setColor(QPalette::Window, theme->color(Theme::Welcome_ForegroundPrimaryColor)); - pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_BackgroundPrimaryColor)); - } else { - pal.setColor(QPalette::Window, theme->color(Theme::Welcome_AccentColor)); - pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_AccentColor)); - } - } else { - if (isCursorInside) { - if (forText) { - pal.setColor(QPalette::Window, theme->color(Theme::Welcome_HoverColor)); - pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor)); - } else { - pal.setColor(QPalette::Window, theme->color(Theme::Welcome_HoverColor)); - pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_ForegroundSecondaryColor)); - } - } else { - if (forText) { - pal.setColor(QPalette::Window, theme->color(Theme::Welcome_ForegroundPrimaryColor)); - pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor)); - } else { - pal.setColor(QPalette::Window, theme->color(Theme::Welcome_BackgroundPrimaryColor)); - pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_ForegroundSecondaryColor)); - } - } - } - return pal; -} - -WelcomePageFrame::WelcomePageFrame(QWidget *parent) - : QWidget(parent) -{ - setContentsMargins(1, 1, 1, 1); -} - -void WelcomePageFrame::paintEvent(QPaintEvent *event) -{ - QWidget::paintEvent(event); - QPainter p(this); - - qDrawPlainRect(&p, rect(), palette().color(QPalette::WindowText), 1); - - if (property(WITHACCENTCOLOR_PROPERTY_NAME).toBool()) { - const int accentRectWidth = 10; - const QRect accentRect = rect().adjusted(width() - accentRectWidth, 0, 0, 0); - p.fillRect(accentRect, creatorTheme()->color(Theme::Welcome_AccentColor)); - } -} - -class WelcomePageButtonPrivate -{ -public: - explicit WelcomePageButtonPrivate(WelcomePageButton *parent) - : q(parent) {} - bool isActive() const; - void doUpdate(bool cursorInside); - - WelcomePageButton *q; - QHBoxLayout *m_layout = nullptr; - QLabel *m_label = nullptr; - - std::function<void()> onClicked; - std::function<bool()> activeChecker; -}; - -WelcomePageButton::WelcomePageButton(QWidget *parent) - : WelcomePageFrame(parent), d(new WelcomePageButtonPrivate(this)) -{ - setAutoFillBackground(true); - setPalette(buttonPalette(false, false, false)); - setContentsMargins(0, 1, 0, 1); - - d->m_label = new QLabel(this); - d->m_label->setPalette(buttonPalette(false, false, true)); - d->m_label->setAlignment(Qt::AlignCenter); - - d->m_layout = new QHBoxLayout; - d->m_layout->setSpacing(0); - d->m_layout->addWidget(d->m_label); - setSize(SizeLarge); - setLayout(d->m_layout); -} - -WelcomePageButton::~WelcomePageButton() -{ - delete d; -} - -void WelcomePageButton::mousePressEvent(QMouseEvent *) -{ - if (d->onClicked) - d->onClicked(); -} - -void WelcomePageButton::enterEvent(QEnterEvent *) -{ - d->doUpdate(true); -} - -void WelcomePageButton::leaveEvent(QEvent *) -{ - d->doUpdate(false); -} - -bool WelcomePageButtonPrivate::isActive() const -{ - return activeChecker && activeChecker(); -} - -void WelcomePageButtonPrivate::doUpdate(bool cursorInside) -{ - const bool active = isActive(); - q->setPalette(WelcomePageFrame::buttonPalette(active, cursorInside, false)); - const QPalette lpal = WelcomePageFrame::buttonPalette(active, cursorInside, true); - m_label->setPalette(lpal); - q->update(); -} - -void WelcomePageButton::setText(const QString &text) -{ - d->m_label->setText(text); -} - -void WelcomePageButton::setSize(Size size) -{ - const int hMargin = size == SizeSmall ? 12 : 26; - const int vMargin = size == SizeSmall ? 2 : 4; - d->m_layout->setContentsMargins(hMargin, vMargin, hMargin, vMargin); -} - -void WelcomePageButton::setWithAccentColor(bool withAccent) -{ - setProperty(WITHACCENTCOLOR_PROPERTY_NAME, withAccent); -} - -void WelcomePageButton::setActiveChecker(const std::function<bool ()> &value) -{ - d->activeChecker = value; -} - -void WelcomePageButton::recheckActive() -{ - bool isActive = d->isActive(); - d->doUpdate(isActive); -} - -void WelcomePageButton::click() -{ - if (d->onClicked) - d->onClicked(); -} - -void WelcomePageButton::setOnClicked(const std::function<void ()> &value) -{ - d->onClicked = value; - if (d->isActive()) - click(); -} - } // namespace Core diff --git a/src/plugins/coreplugin/iwelcomepage.h b/src/plugins/coreplugin/iwelcomepage.h index 9a64dd3a70..1f59a79119 100644 --- a/src/plugins/coreplugin/iwelcomepage.h +++ b/src/plugins/coreplugin/iwelcomepage.h @@ -7,13 +7,10 @@ #include <utils/id.h> -#include <QWidget> #include <QObject> -#include <functional> - QT_BEGIN_NAMESPACE -class QPixmap; +class QWidget; QT_END_NAMESPACE namespace Core { @@ -37,43 +34,4 @@ public: static const QList<IWelcomePage *> allWelcomePages(); }; -class WelcomePageButtonPrivate; - -class CORE_EXPORT WelcomePageFrame : public QWidget -{ -public: - WelcomePageFrame(QWidget *parent); - - void paintEvent(QPaintEvent *event) override; - - static QPalette buttonPalette(bool isActive, bool isCursorInside, bool forText); -}; - -class CORE_EXPORT WelcomePageButton : public WelcomePageFrame -{ -public: - enum Size { - SizeSmall, - SizeLarge, - }; - - explicit WelcomePageButton(QWidget *parent = nullptr); - ~WelcomePageButton() override; - - void mousePressEvent(QMouseEvent *) override; - void enterEvent(QEnterEvent *) override; - void leaveEvent(QEvent *) override; - - void setText(const QString &text); - void setSize(enum Size); - void setWithAccentColor(bool withAccent); - void setOnClicked(const std::function<void ()> &value); - void setActiveChecker(const std::function<bool ()> &value); - void recheckActive(); - void click(); - -private: - WelcomePageButtonPrivate *d; -}; - } // Core diff --git a/src/plugins/coreplugin/session.cpp b/src/plugins/coreplugin/session.cpp index c188f6812c..921046ffed 100644 --- a/src/plugins/coreplugin/session.cpp +++ b/src/plugins/coreplugin/session.cpp @@ -267,6 +267,11 @@ QDateTime SessionManager::lastActiveTime(const QString &session) return d->m_lastActiveTimes.value(session); } +int SessionManager::sessionsCount() +{ + return d->m_sessions.count(); +} + FilePath SessionManager::sessionNameToFileName(const QString &session) { return ICore::userResourcePath(session + ".qws"); diff --git a/src/plugins/coreplugin/session.h b/src/plugins/coreplugin/session.h index 962db5172b..ef51228b01 100644 --- a/src/plugins/coreplugin/session.h +++ b/src/plugins/coreplugin/session.h @@ -34,6 +34,7 @@ public: static QStringList sessions(); static QDateTime sessionDateTime(const QString &session); static QDateTime lastActiveTime(const QString &session); + static int sessionsCount(); static bool createSession(const QString &session); diff --git a/src/plugins/coreplugin/welcomepagehelper.cpp b/src/plugins/coreplugin/welcomepagehelper.cpp index 44fc75270d..636888d6b4 100644 --- a/src/plugins/coreplugin/welcomepagehelper.cpp +++ b/src/plugins/coreplugin/welcomepagehelper.cpp @@ -7,6 +7,7 @@ #include <utils/algorithm.h> #include <utils/fancylineedit.h> +#include <utils/icon.h> #include <utils/layoutbuilder.h> #include <utils/qtcassert.h> #include <utils/stylehelper.h> @@ -26,36 +27,34 @@ #include <qdrawutil.h> + +QT_BEGIN_NAMESPACE +void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0); +QT_END_NAMESPACE + using namespace Utils; namespace Core { using namespace WelcomePageHelpers; +using namespace StyleHelper::SpacingTokens; static QColor themeColor(Theme::Color role) { return creatorTheme()->color(role); } -static QFont sizedFont(int size, const QWidget *widget) -{ - QFont f = widget->font(); - f.setPixelSize(size); - return f; -} - namespace WelcomePageHelpers { -QWidget *panelBar(QWidget *parent) +void setBackgroundColor(QWidget *widget, Theme::Color colorRole) { - auto frame = new QWidget(parent); - frame->setAutoFillBackground(true); - frame->setMinimumWidth(WelcomePageHelpers::HSpacing); - QPalette pal; - pal.setBrush(QPalette::Window, {}); - pal.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundPrimaryColor)); - frame->setPalette(pal); - return frame; + QPalette palette = creatorTheme()->palette(); + const QPalette::ColorRole role = QPalette::Window; + palette.setBrush(role, {}); + palette.setColor(role, creatorTheme()->color(colorRole)); + widget->setPalette(palette); + widget->setBackgroundRole(role); + widget->setAutoFillBackground(true); } void drawCardBackground(QPainter *painter, const QRectF &rect, @@ -77,30 +76,351 @@ void drawCardBackground(QPainter *painter, const QRectF &rect, painter->restore(); } +QWidget *createRule(Qt::Orientation orientation, QWidget *parent) +{ + auto rule = new QWidget(parent); + if (orientation == Qt::Horizontal) + rule->setFixedHeight(1); + else + rule->setFixedWidth(1); + setBackgroundColor(rule, Theme::Token_Stroke_Subtle); + return rule; +} + } // namespace WelcomePageHelpers +enum WidgetState { + WidgetStateDefault, + WidgetStateChecked, + WidgetStateHovered, +}; + +static const TextFormat &buttonTF(Button::Role role, WidgetState state) +{ + using namespace WelcomePageHelpers; + static const TextFormat mediumPrimaryTF + {Theme::Token_Basic_White, StyleHelper::UiElement::UiElementButtonMedium, + Qt::AlignCenter | Qt::TextDontClip}; + static const TextFormat mediumSecondaryTF + {Theme::Token_Text_Default, mediumPrimaryTF.uiElement, mediumPrimaryTF.drawTextFlags}; + static const TextFormat smallPrimaryTF + {mediumPrimaryTF.themeColor, StyleHelper::UiElement::UiElementButtonSmall, + mediumPrimaryTF.drawTextFlags}; + static const TextFormat smallSecondaryTF + {mediumSecondaryTF.themeColor, smallPrimaryTF.uiElement, smallPrimaryTF.drawTextFlags}; + static const TextFormat smallListDefaultTF + {Theme::Token_Text_Default, StyleHelper::UiElement::UiElementIconStandard, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip}; + static const TextFormat smallListCheckedTF + {smallListDefaultTF.themeColor, StyleHelper::UiElement::UiElementIconActive, + smallListDefaultTF.drawTextFlags}; + static const TextFormat smallLinkDefaultTF + {Theme::Token_Text_Default, smallListDefaultTF.uiElement, smallListDefaultTF.drawTextFlags}; + static const TextFormat smallLinkHoveredTF + {Theme::Token_Accent_Default, smallListCheckedTF.uiElement, + smallLinkDefaultTF.drawTextFlags}; + + switch (role) { + case Button::MediumPrimary: return mediumPrimaryTF; + case Button::MediumSecondary: return mediumSecondaryTF; + case Button::SmallPrimary: return smallPrimaryTF; + case Button::SmallSecondary: return smallSecondaryTF; + case Button::SmallList: return (state == WidgetStateDefault) ? smallListDefaultTF + : smallListCheckedTF; + case Button::SmallLink: return (state == WidgetStateDefault) ? smallLinkDefaultTF + : smallLinkHoveredTF; + } + return mediumPrimaryTF; +} + +Button::Button(const QString &text, Role role, QWidget *parent) + : QPushButton(text, parent) + , m_role(role) +{ + // Prevent QMacStyle::subElementRect(SE_PushButtonLayoutItem) from changing our geometry + setFlat(true); + + updateMargins(); + if (m_role == SmallList) + setCheckable(true); + else if (m_role == SmallLink) + setCursor(Qt::PointingHandCursor); +} + +QSize Button::minimumSizeHint() const +{ + const TextFormat &tf = buttonTF(m_role, WidgetStateHovered); + const QFontMetrics fm(tf.font()); + const QSize textS = fm.size(Qt::TextShowMnemonic, text()); + const QMargins margins = contentsMargins(); + return {margins.left() + textS.width() + margins.right(), + margins.top() + tf.lineHeight() + margins.bottom()}; +} + +void Button::paintEvent(QPaintEvent *event) +{ + // Without pixmap + // +----------------+----------------+----------------+ + // | |(VPadding[S|XS])| | + // | +----------------+----------------+ + // |(HPadding[S|XS])| <label> |(HPadding[S|XS])| + // | +----------------+----------------+ + // | |(VPadding[S|XS])| | + // +----------------+---------------------------------+ + // + // With pixmap + // +--------+------------+---------------------------------+ + // | | |(VPadding[S|XS])| | + // | | +----------------+ | + // |<pixmap>|(HGap[S|XS])| <label> |(HPadding[S|XS])| + // | | +----------------+ | + // | | |(VPadding[S|XS])| | + // +--------+------------+----------------+----------------+ + + using namespace WelcomePageHelpers; + const bool hovered = underMouse(); + const WidgetState state = isChecked() ? WidgetStateChecked : hovered ? WidgetStateHovered + : WidgetStateDefault; + const TextFormat &tf = buttonTF(m_role, state); + const QMargins margins = contentsMargins(); + const QRect bgR = rect(); + + QPainter p(this); + + const qreal brRectRounding = 3.75; + switch (m_role) { + case MediumPrimary: + case SmallPrimary: { + const QBrush fill(creatorTheme()->color(isDown() + ? Theme::Token_Accent_Subtle + : hovered ? Theme::Token_Accent_Muted + : Theme::Token_Accent_Default)); + drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding); + break; + } + case MediumSecondary: + case SmallSecondary: { + const QPen outline(creatorTheme()->color(Theme::Token_Text_Default), hovered ? 2 : 1); + drawCardBackground(&p, bgR, QBrush(Qt::NoBrush), outline, brRectRounding); + break; + } + case SmallList: { + if (isChecked() || hovered) { + const QBrush fill(creatorTheme()->color(isChecked() ? Theme::Token_Foreground_Muted + : Theme::Token_Foreground_Subtle)); + drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding); + } + break; + } + case SmallLink: + break; + } + + if (!m_pixmap.isNull()) { + const int pixmapHeight = int(m_pixmap.deviceIndependentSize().height()); + const int pixmapY = (bgR.height() - pixmapHeight) / 2; + p.drawPixmap(0, pixmapY, m_pixmap); + } + + const int availableLabelWidth = event->rect().width() - margins.left() - margins.right(); + const QFont font = tf.font(); + const QFontMetrics fm(font); + const QString elidedLabelText = fm.elidedText(text(), Qt::ElideRight, availableLabelWidth); + const QRect labelR(margins.left(), margins.top(), availableLabelWidth, tf.lineHeight()); + p.setFont(font); + p.setPen(tf.color()); + p.drawText(labelR, tf.drawTextFlags, elidedLabelText); +} + +void Button::setPixmap(const QPixmap &pixmap) +{ + m_pixmap = pixmap; + updateMargins(); +} + +void Button::updateMargins() +{ + const bool tokenSizeS = m_role == MediumPrimary || m_role == MediumSecondary + || m_role == SmallList || m_role == SmallLink; + const int gap = tokenSizeS ? HGapS : HGapXs; + const int hPaddingR = tokenSizeS ? HPaddingS : HPaddingXs; + const int hPaddingL = m_pixmap.isNull() ? hPaddingR + : int(m_pixmap.deviceIndependentSize().width()) + gap; + const int vPadding = tokenSizeS ? VPaddingS : VPaddingXs; + setContentsMargins(hPaddingL, vPadding, hPaddingR, vPadding); +} + +Label::Label(const QString &text, Role role, QWidget *parent) + : QLabel(text, parent) + , m_role(role) +{ + using namespace WelcomePageHelpers; + static const TextFormat primaryTF + {Theme::Token_Text_Muted, StyleHelper::UiElement::UiElementH3}; + static const TextFormat secondaryTF + {primaryTF.themeColor, StyleHelper::UiElement::UiElementH6Capital}; + + const TextFormat &tF = m_role == Primary ? primaryTF : secondaryTF; + const int vPadding = m_role == Primary ? ExPaddingGapM : VPaddingS; + + setFixedHeight(vPadding + tF.lineHeight() + vPadding); + setFont(tF.font()); + QPalette pal = palette(); + pal.setColor(QPalette::WindowText, tF.color()); + setPalette(pal); +} + +constexpr TextFormat searchBoxTextTF + {Theme::Token_Text_Default, StyleHelper::UiElement::UiElementBody2}; +constexpr TextFormat searchBoxPlaceholderTF + {Theme::Token_Text_Muted, searchBoxTextTF.uiElement}; + +static const QPixmap &searchBoxIcon() +{ + static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/search"), + Theme::Token_Text_Muted}}, Icon::Tint).pixmap(); + return icon; +} + SearchBox::SearchBox(QWidget *parent) - : WelcomePageFrame(parent) -{ - setAutoFillBackground(true); - - m_lineEdit = new FancyLineEdit; - m_lineEdit->setFiltering(true); - m_lineEdit->setFrame(false); - m_lineEdit->setMinimumHeight(33); - m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); - - QPalette pal = buttonPalette(false, false, true); - // for the margins - pal.setColor(QPalette::Window, m_lineEdit->palette().color(QPalette::Base)); - // for macOS dark mode - pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_ForegroundPrimaryColor)); - pal.setColor(QPalette::Text, themeColor(Theme::Welcome_TextColor)); + : QLineEdit(parent) +{ + setAttribute(Qt::WA_MacShowFocusRect, false); + setAutoFillBackground(false); + setFont(searchBoxTextTF.font()); + setFrame(false); + setMouseTracking(true); + + QPalette pal = palette(); + pal.setColor(QPalette::Base, Qt::transparent); + pal.setColor(QPalette::PlaceholderText, searchBoxPlaceholderTF.color()); + pal.setColor(QPalette::Text, searchBoxTextTF.color()); setPalette(pal); - auto box = new QHBoxLayout(this); - box->setContentsMargins(10, 0, 1, 0); - box->addWidget(m_lineEdit); + const QSize iconSize = searchBoxIcon().deviceIndependentSize().toSize(); + setContentsMargins({HPaddingXs, ExPaddingGapM, + HPaddingXs + iconSize.width() + HPaddingXs, ExPaddingGapM}); + setFixedHeight(ExPaddingGapM + searchBoxTextTF.lineHeight() + ExPaddingGapM); +} + +QSize SearchBox::minimumSizeHint() const +{ + const QFontMetrics fm(searchBoxTextTF.font()); + const QSize textS = fm.size(Qt::TextSingleLine, text()); + const QMargins margins = contentsMargins(); + return {margins.left() + textS.width() + margins.right(), + margins.top() + searchBoxTextTF.lineHeight() + margins.bottom()}; +} + +void SearchBox::enterEvent(QEnterEvent *event) +{ + QLineEdit::enterEvent(event); + update(); +} + +void SearchBox::leaveEvent(QEvent *event) +{ + QLineEdit::leaveEvent(event); + update(); +} + +static void paintCommonBackground(QPainter *p, const QRectF &rect, const QWidget *widget) +{ + const QBrush fill(creatorTheme()->color(Theme::Token_Background_Muted)); + const Theme::Color c = widget->hasFocus() ? Theme::Token_Stroke_Strong : + widget->underMouse() ? Theme::Token_Stroke_Muted + : Theme::Token_Stroke_Subtle; + const QPen pen(creatorTheme()->color(c)); + drawCardBackground(p, rect, fill, pen); +} + +void SearchBox::paintEvent(QPaintEvent *event) +{ + // +------------+---------------+------------+------+------------+ + // | |(ExPaddingGapM)| | | | + // | +---------------+ | | | + // |(HPaddingXs)| <lineEdit> |(HPaddingXs)|<icon>|(HPaddingXs)| + // | +---------------+ | | | + // | |(ExPaddingGapM)| | | | + // +------------+---------------+------------+------+------------+ + + QPainter p(this); + + paintCommonBackground(&p, rect(), this); + const QPixmap icon = searchBoxIcon(); + const QSize iconS = icon.deviceIndependentSize().toSize(); + const QPoint iconPos(width() - HPaddingXs - iconS.width(), (height() - iconS.height()) / 2); + p.drawPixmap(iconPos, icon); + + QLineEdit::paintEvent(event); +} + +constexpr TextFormat ComboBoxTf + {Theme::Token_Text_Muted, StyleHelper::UiElementIconActive, + Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip}; + +static const QPixmap &comboBoxIcon() +{ + static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/expandarrow"), + ComboBoxTf.themeColor}}, Icon::Tint).pixmap(); + return icon; +} + +ComboBox::ComboBox(QWidget *parent) + : QComboBox(parent) +{ + setFont(ComboBoxTf.font()); + setMouseTracking(true); + + const QSize iconSize = comboBoxIcon().deviceIndependentSize().toSize(); + setContentsMargins({HPaddingXs, VPaddingXs, + HGapXxs + iconSize.width() + HPaddingXs, VPaddingXs}); +} + +QSize ComboBox::sizeHint() const +{ + const QSize parentS = QComboBox::sizeHint(); + const QMargins margins = contentsMargins(); + return {margins.left() + parentS.width() + margins.right(), + margins.top() + ComboBoxTf.lineHeight() + margins.bottom()}; +} + +void ComboBox::enterEvent(QEnterEvent *event) +{ + QComboBox::enterEvent(event); + update(); +} + +void ComboBox::leaveEvent(QEvent *event) +{ + QComboBox::leaveEvent(event); + update(); +} + +void ComboBox::paintEvent(QPaintEvent *) +{ + // +------------+-------------+---------+-------+------------+ + // | | (VPaddingXs)| | | | + // | +-------------+ | | | + // |(HPaddingXs)|<currentItem>|(HGapXxs)|<arrow>|(HPaddingXs)| + // | +-------------+ | | | + // | | (VPaddingXs)| | | | + // +------------+-------------+---------+-------+------------+ + + QPainter p(this); + paintCommonBackground(&p, rect(), this); + + const QMargins margins = contentsMargins(); + const QRect textR(margins.left(), margins.top(), + width() - margins.right(), ComboBoxTf.lineHeight()); + p.setFont(ComboBoxTf.font()); + p.setPen(ComboBoxTf.color()); + p.drawText(textR, ComboBoxTf.drawTextFlags, currentText()); + + const QPixmap icon = comboBoxIcon(); + const QSize iconS = icon.deviceIndependentSize().toSize(); + const QPoint iconPos(width() - HPaddingXs - iconS.width(), (height() - iconS.height()) / 2); + p.drawPixmap(iconPos, icon); } GridView::GridView(QWidget *parent) @@ -115,7 +435,7 @@ GridView::GridView(QWidget *parent) setUniformItemSizes(true); QPalette pal; - pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor)); + pal.setColor(QPalette::Base, themeColor(Theme::Token_Background_Default)); setPalette(pal); // Makes a difference on Mac. } @@ -147,10 +467,11 @@ bool SectionGridView::hasHeightForWidth() const int SectionGridView::heightForWidth(int width) const { - const int columnCount = qMax(1, width / Core::WelcomePageHelpers::GridItemWidth); + const QSize itemSize = ListItemDelegate::itemSize(); + const int columnCount = qMax(1, width / itemSize.width()); const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount; const int maxRowCount = m_maxRows ? std::min(*m_maxRows, rowCount) : rowCount; - return maxRowCount * Core::WelcomePageHelpers::GridItemHeight; + return maxRowCount * itemSize.height(); } void SectionGridView::wheelEvent(QWheelEvent *e) @@ -165,8 +486,9 @@ bool SectionGridView::event(QEvent *e) { if (e->type() == QEvent::Resize) { const auto itemsFit = [this](const QSize &size) { - const int maxColumns = std::max(size.width() / WelcomePageHelpers::GridItemWidth, 1); - const int maxRows = std::max(size.height() / WelcomePageHelpers::GridItemHeight, 1); + const QSize itemSize = ListItemDelegate::itemSize(); + const int maxColumns = std::max(size.width() / itemSize.width(), 1); + const int maxRows = std::max(size.height() / itemSize.height(), 1); const int maxItems = maxColumns * maxRows; const int items = model()->rowCount(); return maxItems >= items; @@ -426,47 +748,123 @@ bool ListModelFilter::leaveFilterAcceptsRowBeforeFiltering(const ListItem *, boo return false; } -ListItemDelegate::ListItemDelegate() - : backgroundPrimaryColor(themeColor(Theme::Welcome_BackgroundPrimaryColor)) - , backgroundSecondaryColor(themeColor(Theme::Welcome_BackgroundSecondaryColor)) - , foregroundPrimaryColor(themeColor(Theme::Welcome_ForegroundPrimaryColor)) - , foregroundSecondaryColor(themeColor(Theme::Welcome_ForegroundSecondaryColor)) - , hoverColor(themeColor(Theme::Welcome_HoverColor)) - , textColor(themeColor(Theme::Welcome_TextColor)) +constexpr TextFormat titleTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive}; +constexpr TextFormat descriptionTF {titleTF.themeColor, StyleHelper::UiElementCaption}; +constexpr TextFormat tagsLabelTF {Theme::Token_Text_Muted, StyleHelper::UiElementCaptionStrong}; +constexpr TextFormat tagsTF {Theme::Token_Accent_Default, tagsLabelTF.uiElement}; + +constexpr qreal itemOutlineWidth = 1; +constexpr qreal itemCornerRounding = 6; +constexpr int thumbnailAreaBorderWidth = 1; +constexpr QSize thumbnailAreaSize = + WelcomeThumbnailSize.grownBy({thumbnailAreaBorderWidth, thumbnailAreaBorderWidth, + thumbnailAreaBorderWidth, thumbnailAreaBorderWidth}); +constexpr int tagsRowsCount = 1; +constexpr int tagsHGap = ExPaddingGapM; + +constexpr QEasingCurve::Type hoverEasing = QEasingCurve::OutCubic; +constexpr std::chrono::milliseconds hoverDuration(300); +constexpr int hoverBlurRadius = 50; +constexpr qreal hoverBlurOpacity = 0.175; + +QSize ListItemDelegate::itemSize() { + const int tagsTfLineHeight = tagsTF.lineHeight(); + const int width = + ExPaddingGapL + + thumbnailAreaSize.width() + + ExPaddingGapL; + const int height = + ExPaddingGapL + + thumbnailAreaSize.height() + + VGapL + + titleTF.lineHeight() + + VGapL + + tagsTfLineHeight + + (tagsTfLineHeight + ExPaddingGapS) * (tagsRowsCount - 1) // If more than one row + + ExPaddingGapL; + return {width + ExVPaddingGapXl, height + ExVPaddingGapXl}; } void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + // Unhovered tile + // +---------------+------------------+---------------+-----------------+ + // | | (ExPaddingGapL) | | | + // | +------------------+ | | + // | | <thumbnail> | | | + // | +------------------+ | | + // | | (VGapL) | | | + // | +------------------+ | | + // |(ExPaddingGapL)| <title> |(ExPaddingGapL)|(ExVPaddingGapXs)| + // | +------------------+ | | + // | | (VGapL) | | | + // | +-----------+------+ | | + // | |<tagsLabel>|<tags>| | | + // | +-----------+------+ | | + // | | (ExPaddingGapL) | | | + // +---------------+------------------+---------------+-----------------+ + // | (ExVPaddingGapXs) | + // +--------------------------------------------------------------------+ + // + // Hovered, final animation state of the are above tagsLabel + // +---------------+------------------+---------------+ + // | | (ExPaddingGapL) | | + // | +------------------+ | + // | | <title> | | + // | +------------------+ | + // | | (ExPaddingGapS) | | + // |(ExPaddingGapL)+------------------+(ExPaddingGapL)| ... + // | | <hr> | | + // | +------------------+ | + // | | (ExPaddingGapS) | | + // | +------------------+ | + // | | <description> | | + // +---------------+------------------+---------------+ + // ... + const ListItem *item = index.data(ListModel::ItemRole).value<Core::ListItem *>(); - const QRect rc = option.rect; - const QRect tileRect(0, 0, rc.width() - GridItemGap, rc.height() - GridItemGap); - const QSize thumbnailBgSize = GridItemImageSize.grownBy(QMargins(1, 1, 1, 1)); - const QRect thumbnailBgRect((tileRect.width() - thumbnailBgSize.width()) / 2, GridItemGap, - thumbnailBgSize.width(), thumbnailBgSize.height()); - const QRect textArea = tileRect.adjusted(GridItemGap, GridItemGap, -GridItemGap, -GridItemGap); + const QFont tagsLabelFont = tagsLabelTF.font(); + const QFontMetrics tagsLabelFM(tagsLabelFont); + const QFont descriptionFont = descriptionTF.font(); + const QFontMetrics descriptionFM(descriptionFont); + + const QRect bgRGlobal = option.rect.adjusted(0, 0, -ExVPaddingGapXl, -ExVPaddingGapXl); + const QRect bgR = bgRGlobal.translated(-option.rect.topLeft()); + const QRect thumbnailAreaR(bgR.left() + ExPaddingGapL, bgR.top() + ExPaddingGapL, + thumbnailAreaSize.width(), thumbnailAreaSize.height()); + const QRect titleR(thumbnailAreaR.left(), thumbnailAreaR.bottom() + VGapL + 1, + thumbnailAreaR.width(), titleTF.lineHeight()); + const QString tagsLabelText = Tr::tr("Tags:"); + const int tagsLabelTextWidth = tagsLabelFM.horizontalAdvance(tagsLabelText); + const QRect tagsLabelR(titleR.left(), titleR.bottom() + VGapL + 1, + tagsLabelTextWidth, tagsTF.lineHeight()); + const QRect tagsR(tagsLabelR.right() + 1 + tagsHGap, tagsLabelR.top(), + bgR.right() - ExPaddingGapL - tagsLabelR.right() - tagsHGap, + tagsLabelR.height() + + (tagsLabelR.height() + ExPaddingGapS) * (tagsRowsCount - 1)); + QTC_CHECK(option.rect.height() == tagsR.bottom() + 1 + ExPaddingGapL + ExVPaddingGapXl); + QTC_CHECK(option.rect.width() == tagsR.right() + 1 + ExPaddingGapL + ExVPaddingGapXl); + + QTextOption wrapTO; + wrapTO.setWrapMode(QTextOption::WordWrap); const bool hovered = option.state & QStyle::State_MouseOver; - constexpr int TagsSeparatorY = GridItemHeight - GridItemGap - 52; - constexpr int tagsBase = TagsSeparatorY + 17; - constexpr int shiftY = TagsSeparatorY - 16; - constexpr int nameY = TagsSeparatorY - 20; - - const QRect textRect = textArea.translated(0, nameY); - const QFont descriptionFont = sizedFont(11, option.widget); - painter->save(); - painter->translate(rc.topLeft()); + painter->translate(bgRGlobal.topLeft()); - painter->fillRect(tileRect, hovered ? hoverColor : backgroundPrimaryColor); + const QColor fill(themeColor(hovered ? Theme::Token_Foreground_Muted + : Theme::Token_Background_Muted)); + const QPen pen(themeColor(hovered ? Theme::Token_Foreground_Muted + : Theme::Token_Stroke_Subtle), itemOutlineWidth); + WelcomePageHelpers::drawCardBackground(painter, bgR, fill, pen, itemCornerRounding); - QTextOption wrapped; - wrapped.setWrapMode(QTextOption::WordWrap); + const int shiftY = thumbnailAreaR.bottom(); int offset = 0; - float animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation + qreal animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation if (hovered) { if (index != m_previousIndex) { m_previousIndex = index; @@ -476,12 +874,12 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti m_currentWidget = qobject_cast<QAbstractItemView *>( const_cast<QWidget *>(option.widget)); } - constexpr float hoverAnimationDuration = 260; - animationProgress = m_startTime.elapsed() / hoverAnimationDuration; + animationProgress = qreal(m_startTime.elapsed()) / hoverDuration.count(); if (animationProgress < 1) { - static const QEasingCurve animationCurve(QEasingCurve::OutCubic); + static const QEasingCurve animationCurve(hoverEasing); offset = animationCurve.valueForProgress(animationProgress) * shiftY; - QTimer::singleShot(10, this, &ListItemDelegate::goon); + using namespace std::chrono_literals; + QTimer::singleShot(10ms, this, &ListItemDelegate::goon); } else { offset = shiftY; } @@ -489,124 +887,125 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti m_previousIndex = QModelIndex(); } - const QRect shiftedTextRect = textRect.adjusted(0, -offset, 0, -offset); - // The pixmap. const QPixmap pm = index.data(ListModel::ItemImageRole).value<QPixmap>(); - QPoint thumbnailPos = thumbnailBgRect.center(); + QPoint thumbnailPos = thumbnailAreaR.center(); if (!pm.isNull()) { - painter->fillRect(thumbnailBgRect, backgroundSecondaryColor); + painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Background_Default)); thumbnailPos.rx() -= pm.width() / pm.devicePixelRatio() / 2 - 1; thumbnailPos.ry() -= pm.height() / pm.devicePixelRatio() / 2 - 1; painter->drawPixmap(thumbnailPos, pm); - painter->setPen(foregroundPrimaryColor); - drawPixmapOverlay(item, painter, option, thumbnailBgRect); + painter->setPen(titleTF.color()); + drawPixmapOverlay(item, painter, option, thumbnailAreaR); } else { // The description text as fallback. - painter->setPen(textColor); - painter->setFont(descriptionFont); - painter->drawText(textArea, item->description, wrapped); + painter->setPen(descriptionTF.color()); + painter->setFont(descriptionTF.font()); + painter->drawText(thumbnailAreaR, item->description, wrapTO); } // The description background + QRect backgroundPortionR = bgR; if (offset) { - QRect backgroundPortionRect = tileRect; - backgroundPortionRect.setTop(shiftY - offset); + backgroundPortionR.setTop(shiftY - offset); if (!pm.isNull()) { if (m_blurredThumbnail.isNull()) { - constexpr int blurRadius = 50; - QImage thumbnail(tileRect.size() + QSize(blurRadius, blurRadius) * 2, + constexpr int filterMargin = hoverBlurRadius; + QImage thumbnail(bgR.size() + QSize(filterMargin, filterMargin) * 2, QImage::Format_ARGB32_Premultiplied); - thumbnail.fill(hoverColor); + thumbnail.fill(themeColor(Theme::Token_Foreground_Muted)); QPainter thumbnailPainter(&thumbnail); - thumbnailPainter.translate(blurRadius, blurRadius); - thumbnailPainter.fillRect(thumbnailBgRect, backgroundSecondaryColor); + thumbnailPainter.translate(filterMargin, filterMargin); + thumbnailPainter.fillRect(thumbnailAreaR, + themeColor(Theme::Token_Background_Default)); thumbnailPainter.drawPixmap(thumbnailPos, pm); - thumbnailPainter.setPen(foregroundPrimaryColor); - drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailBgRect); + thumbnailPainter.setPen(titleTF.color()); + drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailAreaR); + thumbnailPainter.setOpacity(1.0 - hoverBlurOpacity); + thumbnailPainter.fillRect(thumbnail.rect(), + themeColor(Theme::Token_Foreground_Muted)); thumbnailPainter.end(); - - m_blurredThumbnail = QPixmap(tileRect.size()); - QPainter blurredThumbnailPainter(&m_blurredThumbnail); - blurredThumbnailPainter.translate(-blurRadius, -blurRadius); - qt_blurImage(&blurredThumbnailPainter, thumbnail, blurRadius, false, false); - blurredThumbnailPainter.setOpacity(0.825); - blurredThumbnailPainter.fillRect(tileRect, hoverColor); + qt_blurImage(thumbnail, hoverBlurRadius, false, false); + + QImage mask(thumbnail.size(), QImage::Format_Grayscale8); + mask.fill(Qt::black); + QPainter maskPainter(&mask); + const QRect maskR = bgR.translated(filterMargin, filterMargin) + .adjusted(1, 1, -1, -1); + WelcomePageHelpers::drawCardBackground(&maskPainter, maskR, + Qt::white, Qt::NoPen, itemCornerRounding); + thumbnail.setAlphaChannel(mask); + + m_blurredThumbnail = QPixmap::fromImage( + thumbnail.copy({filterMargin, filterMargin, bgR.width(), bgR.height()})); } - const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionRect); - painter->drawPixmap(backgroundPortionRect.topLeft(), thumbnailPortionPM); + const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionR); + painter->drawPixmap(backgroundPortionR.topLeft(), thumbnailPortionPM); } else { - painter->fillRect(backgroundPortionRect, hoverColor); + painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Foreground_Muted)); } } // The description Text (unhovered or hovered) - painter->setPen(textColor); - painter->setFont(sizedFont(13, option.widget)); // Title font + painter->setPen(titleTF.color()); + painter->setFont(titleTF.font()); if (offset) { // The title of the example - const QRectF nameRect = painter->boundingRect(shiftedTextRect, item->name, wrapped); - painter->drawText(nameRect, item->name, wrapped); + const QRect shiftedTitleR = thumbnailAreaR.translated(backgroundPortionR.topLeft()); + const QRect titleR = painter->boundingRect(shiftedTitleR, item->name, wrapTO).toRect(); + painter->drawText(shiftedTitleR, item->name, wrapTO); - // The separator line below the example title. - const int ll = nameRect.height() + 3; - const QLine line = QLine(0, ll, textArea.width(), ll).translated(shiftedTextRect.topLeft()); - painter->setPen(foregroundSecondaryColor); painter->setOpacity(animationProgress); // "fade in" separator line and description - painter->drawLine(line); + + // The separator line below the example title. + const QRect hrR(titleR.x(), titleR.bottom() + 1 + ExPaddingGapS, thumbnailAreaR.width(), 1); + painter->fillRect(hrR, themeColor(Theme::Token_Stroke_Muted)); // The description text. - const int dd = ll + 5; - const QRect descRect = shiftedTextRect.adjusted(0, dd, 0, dd); - painter->setPen(textColor); - painter->setFont(descriptionFont); - painter->drawText(descRect, item->description, wrapped); + const QRect descriptionR(hrR.x(), hrR.bottom() + 1 + ExPaddingGapS, + thumbnailAreaR.width(), shiftY); + painter->setPen(descriptionTF.color()); + painter->setFont(descriptionTF.font()); + painter->drawText(descriptionR, item->description, wrapTO); painter->setOpacity(1); } else { // The title of the example const QString elidedName = painter->fontMetrics() - .elidedText(item->name, Qt::ElideRight, textRect.width()); - painter->drawText(textRect, elidedName); + .elidedText(item->name, Qt::ElideRight, titleR.width()); + painter->drawText(titleR, titleTF.drawTextFlags, elidedName); } - // Separator line between text and 'Tags:' section - painter->setPen(foregroundSecondaryColor); - painter->drawLine(QLineF(textArea.topLeft(), textArea.topRight()) - .translated(0, TagsSeparatorY)); - // The 'Tags:' section - painter->setPen(foregroundPrimaryColor); - const QFont tagsFont = sizedFont(10, option.widget); - painter->setFont(tagsFont); + painter->setPen(tagsLabelTF.color()); + painter->setFont(tagsLabelTF.font()); + painter->drawText(tagsLabelR, tagsLabelTF.drawTextFlags, tagsLabelText); + const QFontMetrics fm = painter->fontMetrics(); - const QString tagsLabelText = Tr::tr("Tags:"); - constexpr int tagsHorSpacing = 5; - const QRect tagsLabelRect = - QRect(0, 0, fm.horizontalAdvance(tagsLabelText) + tagsHorSpacing, fm.height()) - .translated(textArea.x(), tagsBase); - painter->drawText(tagsLabelRect, tagsLabelText); - - painter->setPen(themeColor(Theme::Welcome_LinkColor)); - int emptyTagRowsLeft = 2; + + painter->setPen(tagsTF.color()); + painter->setFont(tagsTF.font()); + int emptyTagRowsLeft = tagsRowsCount; int xx = 0; int yy = 0; const bool populateTagsRects = m_currentTagRects.empty(); for (const QString &tag : item->tags) { - const int ww = fm.horizontalAdvance(tag) + tagsHorSpacing; - if (xx + ww > textArea.width() - tagsLabelRect.width()) { + const int ww = fm.horizontalAdvance(tag); + if (xx + ww > tagsR.width()) { if (--emptyTagRowsLeft == 0) break; yy += fm.lineSpacing(); xx = 0; } - const QRect tagRect = QRect(xx, yy, ww, tagsLabelRect.height()) - .translated(tagsLabelRect.topRight()); - painter->drawText(tagRect, tag); - if (populateTagsRects) - m_currentTagRects.append({ tag, tagRect }); - xx += ww; + const QRect tagRect = QRect(xx, yy, ww, tagsLabelR.height()).translated(tagsR.topLeft()); + painter->drawText(tagRect, tagsTF.drawTextFlags, tag); + if (populateTagsRects) { + constexpr int grow = tagsHGap / 2; + const QRect tagMouseArea = tagRect.adjusted(-grow, -grow, grow, grow); + m_currentTagRects.append({ tag, tagMouseArea }); + } + xx += ww + tagsHGap; } painter->restore(); @@ -642,7 +1041,7 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const { - return {GridItemWidth, GridItemHeight}; + return itemSize(); } void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *, @@ -682,6 +1081,7 @@ SectionedGridView::SectionedGridView(QWidget *parent) auto sectionedView = new QWidget; auto layout = new QVBoxLayout; + layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); layout->addStretch(1); sectionedView->setLayout(layout); @@ -744,24 +1144,24 @@ void SectionedGridView::setSearchString(const QString &searchString) filterModel->setSearchString(searchString); } -static QWidget *createSeparator(QWidget *parent) +static QLabel *createTitleLabel(const QString &text, QWidget *parent = nullptr) { - QWidget *line = Layouting::createHr(parent); - QSizePolicy linePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored); - linePolicy.setHorizontalStretch(2); - line->setSizePolicy(linePolicy); - QPalette pal = line->palette(); - pal.setColor(QPalette::Dark, Qt::transparent); - pal.setColor(QPalette::Light, themeColor(Theme::Welcome_ForegroundSecondaryColor)); - line->setPalette(pal); - return line; + constexpr TextFormat headerTitleTF {Theme::Token_Text_Muted, StyleHelper::UiElementH4}; + auto link = new QLabel(text, parent); + link->setFont(headerTitleTF.font()); + QPalette pal = link->palette(); + pal.setColor(QPalette::WindowText, headerTitleTF.color()); + link->setPalette(pal); + return link; } static QLabel *createLinkLabel(const QString &text, QWidget *parent) { - const QString linkColor = themeColor(Theme::Welcome_LinkColor).name(); + constexpr TextFormat headerLinkTF {Theme::Token_Accent_Default, StyleHelper::UiElementH6}; + const QString linkColor = themeColor(headerLinkTF.themeColor).name(); auto link = new QLabel("<a href=\"link\" style=\"color: " + linkColor + ";\">" - + text + "</a>", parent); + + text + "</a>", parent); + link->setFont(headerLinkTF.font()); return link; } @@ -797,14 +1197,13 @@ ListModel *SectionedGridView::addSection(const Section §ion, const QList<Lis connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); }); using namespace Layouting; QWidget *sectionLabel = Row { - section.name, - createSeparator(this), + createTitleLabel(section.name), + st, seeAllLink, - Space(HSpacing), - noMargin + Space(ExVPaddingGapXl), + customMargin({0, ExPaddingGapL, 0, VPaddingL}), }.emerge(); m_sectionLabels.append(sectionLabel); - sectionLabel->setContentsMargins(0, ItemGap, 0, 0); auto scrollArea = qobject_cast<QScrollArea *>(widget(0)); auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout()); @@ -858,6 +1257,7 @@ void SectionedGridView::zoomInSection(const Section §ion) auto zoomedInWidget = new QWidget(this); auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); zoomedInWidget->setLayout(layout); QLabel *backLink = createLinkLabel("< " + Tr::tr("Back"), this); @@ -868,13 +1268,12 @@ void SectionedGridView::zoomInSection(const Section §ion) }); using namespace Layouting; QWidget *sectionLabel = Row { - section.name, - createSeparator(this), + createTitleLabel(section.name), + st, backLink, - Space(HSpacing), - noMargin + Space(ExVPaddingGapXl), + customMargin({0, ExPaddingGapL, 0, VPaddingL}), }.emerge(); - sectionLabel->setContentsMargins(0, ItemGap, 0, 0); auto gridView = new GridView(zoomedInWidget); gridView->setItemDelegate(m_itemDelegate); diff --git a/src/plugins/coreplugin/welcomepagehelper.h b/src/plugins/coreplugin/welcomepagehelper.h index c1792c0872..6856a59a99 100644 --- a/src/plugins/coreplugin/welcomepagehelper.h +++ b/src/plugins/coreplugin/welcomepagehelper.h @@ -6,10 +6,17 @@ #include "core_global.h" #include "iwelcomepage.h" +#include <utils/fancylineedit.h> +#include <utils/stylehelper.h> +#include <utils/theme/theme.h> + +#include <QComboBox> #include <QElapsedTimer> +#include <QLabel> #include <QListView> #include <QPen> #include <QPointer> +#include <QPushButton> #include <QSortFilterProxyModel> #include <QStackedWidget> #include <QStyledItemDelegate> @@ -24,31 +31,111 @@ namespace Core { namespace WelcomePageHelpers { -constexpr int HSpacing = 20; -constexpr int ItemGap = 4; +constexpr QSize WelcomeThumbnailSize(214, 160); + +class CORE_EXPORT TextFormat { +public: + QColor color() const + { + return Utils::creatorTheme()->color(themeColor); + } + + QFont font(bool underlined = false) const + { + QFont result = Utils::StyleHelper::uiFont(uiElement); + result.setUnderline(underlined); + return result; + } + + int lineHeight() const + { + return Utils::StyleHelper::uiFontLineHeight(uiElement); + } -constexpr int GridItemGap = 3 * ItemGap; -constexpr int GridItemWidth = 240 + GridItemGap; // Extra GridItemGap as "spacing" -constexpr int GridItemHeight = GridItemWidth; -constexpr QSize GridItemImageSize(GridItemWidth - GridItemGap - - 2 * (GridItemGap + 1), // Horizontal margins + 1 pixel - GridItemHeight - GridItemGap - - GridItemGap - 1 // Upper margin + 1 pixel - - 67); // Bottom margin (for title + tags) + const Utils::Theme::Color themeColor; + const Utils::StyleHelper::UiElement uiElement; + const int drawTextFlags = Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip; +}; -CORE_EXPORT QWidget *panelBar(QWidget *parent = nullptr); +CORE_EXPORT void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole); +constexpr qreal defaultCardBackgroundRounding = 3.75; CORE_EXPORT void drawCardBackground(QPainter *painter, const QRectF &rect, const QBrush &fill, const QPen &pen = QPen(Qt::NoPen), - qreal rounding = 5.0); + qreal rounding = defaultCardBackgroundRounding); +CORE_EXPORT QWidget *createRule(Qt::Orientation orientation, QWidget *parent = nullptr); } // namespace WelcomePageHelpers -class CORE_EXPORT SearchBox : public WelcomePageFrame +class CORE_EXPORT Button : public QPushButton +{ +public: + enum Role { + MediumPrimary, + MediumSecondary, + SmallPrimary, + SmallSecondary, + SmallList, + SmallLink, + }; + + explicit Button(const QString &text, Role role, QWidget *parent = nullptr); + + QSize minimumSizeHint() const override; + + void setPixmap(const QPixmap &newPixmap); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void updateMargins(); + + const Role m_role = MediumPrimary; + QPixmap m_pixmap; +}; + +class CORE_EXPORT Label : public QLabel +{ +public: + enum Role { + Primary, + Secondary, + }; + + explicit Label(const QString &text, Role role, QWidget *parent = nullptr); + +private: + const Role m_role = Primary; +}; + +class CORE_EXPORT SearchBox : public QLineEdit { public: explicit SearchBox(QWidget *parent = nullptr); - Utils::FancyLineEdit *m_lineEdit = nullptr; + QSize minimumSizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +protected: + void enterEvent(QEnterEvent *event) override; + void leaveEvent(QEvent *event) override; +}; + +class CORE_EXPORT ComboBox : public QComboBox +{ +public: + explicit ComboBox(QWidget *parent = nullptr); + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +protected: + void enterEvent(QEnterEvent *event) override; + void leaveEvent(QEvent *event) override; }; class CORE_EXPORT GridView : public QListView @@ -146,7 +233,9 @@ class CORE_EXPORT ListItemDelegate : public QStyledItemDelegate { Q_OBJECT public: - ListItemDelegate(); + ListItemDelegate() = default; + + static QSize itemSize(); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; @@ -165,13 +254,6 @@ protected: void goon(); - const QColor backgroundPrimaryColor; - const QColor backgroundSecondaryColor; - const QColor foregroundPrimaryColor; - const QColor foregroundSecondaryColor; - const QColor hoverColor; - const QColor textColor; - private: mutable QPersistentModelIndex m_previousIndex; mutable QElapsedTimer m_startTime; diff --git a/src/plugins/extensionmanager/extensionmanagerwidget.cpp b/src/plugins/extensionmanager/extensionmanagerwidget.cpp index 17dc808b8d..be7e0f92ee 100644 --- a/src/plugins/extensionmanager/extensionmanagerwidget.cpp +++ b/src/plugins/extensionmanager/extensionmanagerwidget.cpp @@ -29,14 +29,6 @@ using namespace Utils; namespace ExtensionManager::Internal { -static QWidget *createVr(QWidget *parent = nullptr) -{ - auto vr = new QWidget(parent); - vr->setFixedWidth(1); - setBackgroundColor(vr, Theme::Token_Stroke_Subtle); - return vr; -} - class CollapsingWidget : public QWidget { public: @@ -79,13 +71,13 @@ ExtensionManagerWidget::ExtensionManagerWidget() using namespace Layouting; Row { - createVr(), + WelcomePageHelpers::createRule(Qt::Vertical), m_secondaryDescription, noMargin(), spacing(0), }.attachTo(m_secondarDescriptionWidget); Row { - createVr(), + WelcomePageHelpers::createRule(Qt::Vertical), Row { m_primaryDescription, noMargin(), @@ -95,13 +87,13 @@ ExtensionManagerWidget::ExtensionManagerWidget() }.attachTo(descriptionColumns); Row { - Space(WelcomePageHelpers::HSpacing), - m_leftColumn, + Space(StyleHelper::SpacingTokens::ExVPaddingGapXl), + m_leftColumn, descriptionColumns, noMargin(), spacing(0), }.attachTo(this); - setBackgroundColor(this, Theme::Token_Background_Default); + WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default); connect(m_leftColumn, &ExtensionsBrowser::itemSelected, this, &ExtensionManagerWidget::updateView); @@ -132,7 +124,7 @@ void ExtensionManagerWidget::updateView(const QModelIndex ¤t, "margin-left: %3px; margin-right: %3px;") .arg(creatorTheme()->color(Theme::Token_Text_Default).name()) .arg(creatorTheme()->color(Theme::Token_Background_Muted).name()) - .arg(WelcomePageHelpers::HSpacing); + .arg(StyleHelper::SpacingTokens::ExVPaddingGapXl); const QString htmlStart = QString(R"( <html> <body style="%1"> diff --git a/src/plugins/extensionmanager/extensionsbrowser.cpp b/src/plugins/extensionmanager/extensionsbrowser.cpp index 1758694ab9..dba1936d60 100644 --- a/src/plugins/extensionmanager/extensionsbrowser.cpp +++ b/src/plugins/extensionmanager/extensionsbrowser.cpp @@ -41,7 +41,7 @@ using PluginSpecList = QList<const PluginSpec *>; using Tags = QStringList; constexpr QSize itemSize = {330, 86}; -constexpr int gapSize = 2 * WelcomePageHelpers::GridItemGap; +constexpr int gapSize = StyleHelper::SpacingTokens::ExVPaddingGapXl; constexpr QSize cellSize = {itemSize.width() + gapSize, itemSize.height() + gapSize}; enum Role { @@ -62,16 +62,6 @@ ItemData itemData(const QModelIndex &index) }; } -void setBackgroundColor(QWidget *widget, Theme::Color colorRole) -{ - QPalette palette = creatorTheme()->palette(); - palette.setColor(QPalette::Window, - creatorTheme()->color(colorRole)); - widget->setPalette(palette); - widget->setBackgroundRole(QPalette::Window); - widget->setAutoFillBackground(true); -} - static QColor colorForExtensionName(const QString &name) { const size_t hash = qHash(name); @@ -390,7 +380,7 @@ public: } { constexpr int textX = 80; - constexpr int rightMargin = 2 * WelcomePageHelpers::ItemGap; + constexpr int rightMargin = StyleHelper::SpacingTokens::ExVPaddingGapXl; constexpr int maxTextWidth = itemSize.width() - textX - rightMargin; constexpr Qt::TextElideMode elideMode = Qt::ElideRight; @@ -440,8 +430,7 @@ ExtensionsBrowser::ExtensionsBrowser() m_searchBox = new Core::SearchBox; m_searchBox->setFixedWidth(itemSize.width()); - m_updateButton = new WelcomePageButton; - m_updateButton->setText(Tr::tr("Install...")); + m_updateButton = new Button(Tr::tr("Install..."), Button::MediumPrimary); m_filterProxyModel = new QSortFilterProxyModel(this); m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); @@ -469,9 +458,10 @@ ExtensionsBrowser::ExtensionsBrowser() noMargin(), spacing(0), }.attachTo(this); - setBackgroundColor(this, Theme::Token_Background_Default); - setBackgroundColor(m_extensionsView, Theme::Token_Background_Default); - setBackgroundColor(m_extensionsView->viewport(), Theme::Token_Background_Default); + WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default); + WelcomePageHelpers::setBackgroundColor(m_extensionsView, Theme::Token_Background_Default); + WelcomePageHelpers::setBackgroundColor(m_extensionsView->viewport(), + Theme::Token_Background_Default); auto updateModel = [this] { m_model.reset(extensionsModel()); @@ -488,7 +478,7 @@ ExtensionsBrowser::ExtensionsBrowser() connect(ExtensionSystem::PluginManager::instance(), &ExtensionSystem::PluginManager::pluginsChanged, this, updateModel); - connect(m_searchBox->m_lineEdit, &Utils::FancyLineEdit::textChanged, + connect(m_searchBox, &QLineEdit::textChanged, m_filterProxyModel, &QSortFilterProxyModel::setFilterWildcard); } diff --git a/src/plugins/extensionmanager/extensionsbrowser.h b/src/plugins/extensionmanager/extensionsbrowser.h index 7a41becf0e..4724e53cf5 100644 --- a/src/plugins/extensionmanager/extensionsbrowser.h +++ b/src/plugins/extensionmanager/extensionsbrowser.h @@ -10,7 +10,9 @@ QT_BEGIN_NAMESPACE class QItemSelectionModel; +class QLineEdit; class QListView; +class QPushButton; class QSortFilterProxyModel; QT_END_NAMESPACE @@ -19,11 +21,6 @@ namespace ExtensionSystem class PluginSpec; } -namespace Core { -class SearchBox; -class WelcomePageButton; -} - namespace ExtensionManager::Internal { using PluginSpecList = QList<const ExtensionSystem::PluginSpec *>; @@ -42,7 +39,6 @@ struct ItemData { }; ItemData itemData(const QModelIndex &index); -void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole); class ExtensionsBrowser final : public QWidget { @@ -61,8 +57,8 @@ private: int extraListViewWidth() const; // Space for scrollbar, etc. QScopedPointer<QStandardItemModel> m_model; - Core::SearchBox *m_searchBox; - Core::WelcomePageButton *m_updateButton; + QLineEdit *m_searchBox; + QPushButton *m_updateButton; QListView *m_extensionsView; QItemSelectionModel *m_selectionModel = nullptr; QSortFilterProxyModel *m_filterProxyModel; diff --git a/src/plugins/marketplace/productlistmodel.cpp b/src/plugins/marketplace/productlistmodel.cpp index 1de5a4b833..4d36c84db4 100644 --- a/src/plugins/marketplace/productlistmodel.cpp +++ b/src/plugins/marketplace/productlistmodel.cpp @@ -269,7 +269,7 @@ void SectionedProducts::onImageDownloadFinished(QNetworkReply *reply) if (pixmap.loadFromData(data, imageFormat.toLatin1())) { const QString url = imageUrl.toString(); const int dpr = qApp->devicePixelRatio(); - pixmap = pixmap.scaled(WelcomePageHelpers::GridItemImageSize * dpr, + pixmap = pixmap.scaled(WelcomePageHelpers::WelcomeThumbnailSize * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation); pixmap.setDevicePixelRatio(dpr); diff --git a/src/plugins/marketplace/qtmarketplacewelcomepage.cpp b/src/plugins/marketplace/qtmarketplacewelcomepage.cpp index a81f252b03..e315ce7efb 100644 --- a/src/plugins/marketplace/qtmarketplacewelcomepage.cpp +++ b/src/plugins/marketplace/qtmarketplacewelcomepage.cpp @@ -9,9 +9,10 @@ #include <coreplugin/welcomepagehelper.h> #include <utils/fancylineedit.h> +#include <utils/layoutbuilder.h> #include <utils/progressindicator.h> -#include <utils/theme/theme.h> #include <utils/qtcassert.h> +#include <utils/theme/theme.h> #include <QDesktopServices> #include <QLabel> @@ -44,38 +45,34 @@ class QtMarketplacePageWidget : public QWidget public: QtMarketplacePageWidget() { - auto searchBox = new Core::SearchBox(this); - m_searcher = searchBox->m_lineEdit; + m_searcher = new Core::SearchBox(this); m_searcher->setPlaceholderText(Tr::tr("Search in Marketplace...")); - auto vbox = new QVBoxLayout(this); - vbox->setContentsMargins(0, 0, 0, Core::WelcomePageHelpers::ItemGap); - vbox->setSpacing(Core::WelcomePageHelpers::ItemGap); - - auto searchBar = Core::WelcomePageHelpers::panelBar(); - auto hbox = new QHBoxLayout(searchBar); - hbox->setContentsMargins(Core::WelcomePageHelpers::HSpacing, 0, - Core::WelcomePageHelpers::HSpacing, 0); - hbox->addWidget(searchBox); - vbox->addWidget(searchBar); m_errorLabel = new QLabel(this); m_errorLabel->setVisible(false); - vbox->addWidget(m_errorLabel); - auto resultWidget = new QWidget(this); - auto resultHBox = new QHBoxLayout(resultWidget); - resultHBox->setContentsMargins(Core::WelcomePageHelpers::HSpacing, 0, 0, 0); m_sectionedProducts = new SectionedProducts(this); auto progressIndicator = new Utils::ProgressIndicator(ProgressIndicatorSize::Large, this); progressIndicator->attachToWidget(m_sectionedProducts); progressIndicator->hide(); - resultHBox->addWidget(m_sectionedProducts); - vbox->addWidget(resultWidget); + + using namespace StyleHelper::SpacingTokens; + + using namespace Layouting; + Column { + Row { + m_searcher, + customMargin({0, 0, ExVPaddingGapXl, 0}), + }, + m_sectionedProducts, + spacing(VPaddingL), + customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}), + }.attachTo(this); connect(m_sectionedProducts, &SectionedProducts::toggleProgressIndicator, progressIndicator, &Utils::ProgressIndicator::setVisible); connect(m_sectionedProducts, &SectionedProducts::errorOccurred, this, - [this, progressIndicator, searchBox](int, const QString &message) { + [this, progressIndicator](int, const QString &message) { progressIndicator->hide(); progressIndicator->deleteLater(); m_errorLabel->setAlignment(Qt::AlignHCenter); @@ -89,7 +86,7 @@ public: "</p><br/><p><small><i>Error: %1</i></small></p>").arg(message); m_errorLabel->setText(txt); m_errorLabel->setVisible(true); - searchBox->setVisible(false); + m_searcher->setVisible(false); connect(m_errorLabel, &QLabel::linkActivated, this, []() { QDesktopServices::openUrl(QUrl("https://marketplace.qt.io")); }); }); diff --git a/src/plugins/projectexplorer/projectwelcomepage.cpp b/src/plugins/projectexplorer/projectwelcomepage.cpp index 45524bdb16..431fa8ee1d 100644 --- a/src/plugins/projectexplorer/projectwelcomepage.cpp +++ b/src/plugins/projectexplorer/projectwelcomepage.cpp @@ -21,6 +21,7 @@ #include <utils/algorithm.h> #include <utils/fileutils.h> #include <utils/icon.h> +#include <utils/layoutbuilder.h> #include <utils/qtcassert.h> #include <utils/stringutils.h> #include <utils/stylehelper.h> @@ -28,9 +29,6 @@ #include <QAbstractItemDelegate> #include <QAction> -#include <QBoxLayout> -#include <QDir> -#include <QFileInfo> #include <QHeaderView> #include <QHelpEvent> #include <QLabel> @@ -42,16 +40,52 @@ using namespace Core; using namespace Core::WelcomePageHelpers; using namespace Utils; +using namespace Utils::StyleHelper::SpacingTokens; -const int LINK_HEIGHT = 35; -const int TEXT_OFFSET_HORIZONTAL = 36; -const int SESSION_LINE_HEIGHT = 28; -const int SESSION_ARROW_RECT_WIDTH = 24; const char PROJECT_BASE_ID[] = "Welcome.OpenRecentProject"; namespace ProjectExplorer { namespace Internal { +constexpr TextFormat projectNameTF {Theme::Token_Accent_Default, StyleHelper::UiElementH5}; +constexpr TextFormat projectPathTF {Theme::Token_Text_Muted, StyleHelper::UiElementIconActive}; +constexpr TextFormat sessionNameTF {projectNameTF.themeColor, projectNameTF.uiElement}; +constexpr TextFormat sessionProjetNameTF {Theme::Token_Text_Default, projectNameTF.uiElement}; +constexpr TextFormat shortcutNumberTF {Theme::Token_Text_Default, + StyleHelper::UiElementCaptionStrong, + Qt::AlignCenter | Qt::TextDontClip}; +constexpr TextFormat actionTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive, + Qt::AlignCenter | Qt::TextDontClip}; +constexpr TextFormat actionDisabledTF {Theme::Token_Text_Subtle, actionTF.uiElement, + actionTF.drawTextFlags}; +constexpr int shortcutNumberWidth = 16; +constexpr int actionSepWidth = 1; +constexpr int sessionScrollBarGap = HPaddingXs; + +static int s(const int metric) +{ + constexpr int shrinkWhenAbove = 150; // Above this session count, increasingly reduce scale + constexpr qreal maxScale = 1.0; // Spacings as defined by design + constexpr qreal minScale = 0.2; // Maximum "condensed" layout + + const int sessionsCount = SessionManager::sessionsCount(); + const qreal scaling = sessionsCount < shrinkWhenAbove + ? maxScale + : qMax(minScale, + maxScale - (sessionsCount - shrinkWhenAbove) * 0.065); + return int(qMax(1.0, scaling * metric)); +} + +static int itemSpacing() +{ + return qMax(int(s(VGapL)), VGapS); +} + +static bool withIcon() +{ + return s(100) > 60; // Hide icons if spacings are scaled to below 60% +} + ProjectModel::ProjectModel(QObject *parent) : QAbstractListModel(parent) { @@ -61,7 +95,7 @@ ProjectModel::ProjectModel(QObject *parent) int ProjectModel::rowCount(const QModelIndex &) const { - return m_projects.count(); + return int(m_projects.count()); } QVariant ProjectModel::data(const QModelIndex &index, int role) const @@ -190,19 +224,22 @@ static QColor themeColor(Theme::Color role) return Utils::creatorTheme()->color(role); } -static QFont sizedFont(int size, const QWidget *widget, - bool underline = false) +static QPixmap pixmap(const QString &id, const Theme::Color color) { - QFont f = widget->font(); - f.setPixelSize(size); - f.setUnderline(underline); - return f; + const QString fileName = QString(":/welcome/images/%1.png").arg(id); + return Icon({{FilePath::fromString(fileName), color}}, Icon::Tint).pixmap(); } -static QPixmap pixmap(const QString &id, const Theme::Color &color) +static void drawBackgroundRect(QPainter *painter, const QRectF &rect, bool hovered) { - const QString fileName = QString(":/welcome/images/%1.png").arg(id); - return Icon({{FilePath::fromString(fileName), color}}, Icon::Tint).pixmap(); + const QColor fill(themeColor(hovered ? Theme::Token_Foreground_Muted + : Theme::Token_Background_Muted)); + const QPen pen(themeColor(hovered ? Theme::Token_Foreground_Muted + : Theme::Token_Stroke_Subtle)); + + const qreal rounding = s(defaultCardBackgroundRounding * 1000) / 1000.0; + const qreal saneRounding = rounding <= 2 ? 0 : rounding; + WelcomePageHelpers::drawCardBackground(painter, rect, fill, pen, saneRounding); } class BaseDelegate : public QAbstractItemDelegate @@ -244,6 +281,11 @@ protected: class SessionDelegate : public BaseDelegate { protected: + bool expanded(const QModelIndex &idx) const + { + return m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString()); + } + QString entryType() override { return Tr::tr("session", "Appears in \"Open session <name>\""); @@ -252,150 +294,287 @@ protected: { // in expanded state bottom contains 'Clone', 'Rename', etc links, where the tool tip // would be confusing - const bool expanded = m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString()); - return expanded ? itemRect.adjusted(0, 0, 0, -LINK_HEIGHT) : itemRect; + return expanded(idx) ? itemRect.adjusted(0, 0, 0, -actionButtonHeight()) : itemRect; } - int shortcutRole() const override { return SessionModel::ShortcutRole; } -public: - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final + int shortcutRole() const override { - static const QPixmap sessionIcon = pixmap("session", Theme::Welcome_ForegroundSecondaryColor); + return SessionModel::ShortcutRole; + } - const QRect rc = option.rect; - const QString sessionName = idx.data(Qt::DisplayRole).toString(); + static int actionButtonHeight() + { + return s(VPaddingXxs) + actionTF.lineHeight() + s(VPaddingXxs); + } + + static const QPixmap &icon() + { + static const QPixmap icon = pixmap("session", Theme::Token_Text_Muted); + return icon; + } + +public: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &idx) const final + { + // visible on withIcon() Gap + arrow visible on hover Extra margin right of project item + // | | | + // +----------+----------+ +--------+-------+ +----------+----------+ + // | | | | | | + // + // +------------+--------+--------+------------+--------+-------------+--------+-------+------------+---------------------+ --+ + // | | | |(VPaddingXs)| |(VPaddingXs) | | | | | | + // | | | +------------+ +-------------+ | | | | | + // |(HPaddingXs)|<number>|(HGapXs)| <icon> |(HGapXs)|<sessionName>|(HGapXs)|<arrow>| | | +-- Header + // | |(16x16) | +------------+ +-------------+ | | | | | + // | | | |(VPaddingXs)| |(VPaddingXs) | | | | | | + // |------------+--------+--------+------------+--------+-------------+--------+-------+ | | --+ + // | +-- | (VPaddingXs) | | | | + // | | +------------------------------+(HPaddingXs)| | | + // | | | <projectName> | | | | + // | | +------------------------------+ | | | + // | Per project in session --+ | (EXSPaddingGapS) | |(sessionScrollBarGap)| | + // | | +------------------------------+ | | | + // | | | <projectPath> | | | | + // | | +------------------------------+ | | +-- Expansion + // | +-- | (VPaddingXs) | | | | + // +----------------------------------------------+------------------------------------+------------+ | | + // | (VPaddingXs) | | | + // +----------------------------------------+--------------+----------------------------------------+ | | + // +-- | <cloneButton>|<renameButton>|<deleteButton> | | | + // | +----------------------------------------+--------------+----------------------------------------+ | | + // | | (VPaddingXs) | | | + // | +------------------------------------------------------------------------------------------------+---------------------+ --+ + // | | (VGapL) | +-- Gap between session items + // | +----------------------------------------------------------------------------------------------------------------------+ --+ + // | + // \ session action "buttons" and dividers + // +-----------------------------------------------+--------+---------+--------+ + // | (VGapXs) | | | | + // +----------------+-------------+----------------+ | | | + // |(EXSPaddingGapM)|<buttonLabel>|(EXSPaddingGapM)|(HGapXs)|<divider>|(HGapXs)| + // +----------------+-------------+----------------+ |(w:1) | | + // | (VGapXs) | | | | + // +-----------------------------------------------+--------+---------+--------+ + // + // | | + // +-------------+-------------+ + // | + // omitted after last button const QPoint mousePos = option.widget->mapFromGlobal(QCursor::pos()); - //const bool hovered = option.state & QStyle::State_MouseOver; const bool hovered = option.rect.contains(mousePos); - const bool expanded = m_expandedSessions.contains(sessionName); - painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor)); - painter->fillRect(rc.adjusted(0, 0, 0, -ItemGap), - hovered ? hoverColor : backgroundPrimaryColor); - - const int x = rc.x(); - const int x1 = x + TEXT_OFFSET_HORIZONTAL; - const int y = rc.y(); - const int firstBase = y + 18; - - painter->drawPixmap(x + 11, y + 6, sessionIcon); - - if (hovered && !expanded) { - const QRect arrowRect = rc.adjusted(rc.width() - SESSION_ARROW_RECT_WIDTH, 0, 0, 0); - const bool arrowRectHovered = arrowRect.contains(mousePos); - painter->fillRect(arrowRect.adjusted(0, 0, 0, -ItemGap), - arrowRectHovered ? hoverColor : backgroundPrimaryColor); - } + const bool expanded = this->expanded(idx); - if (hovered || expanded) { - static const QPixmap arrowUp = pixmap("expandarrow",Theme::Welcome_ForegroundSecondaryColor); - static const QPixmap arrowDown = QPixmap::fromImage(arrowUp.toImage().mirrored(false, true)); - painter->drawPixmap(rc.right() - 19, y + 6, expanded ? arrowDown : arrowUp); - } + const QRect bgR = option.rect.adjusted(0, 0, -sessionScrollBarGap, -itemSpacing()); + const QRect hdR(bgR.topLeft(), QSize(bgR.width(), expanded ? headerHeight() + : bgR.height())); + + const QSize iconS = icon().deviceIndependentSize().toSize(); + static const QPixmap arrow = Icon({{FilePath::fromString(":/core/images/expandarrow"), + Theme::Token_Text_Muted}}, Icon::Tint).pixmap(); + const QSize arrowS = arrow.deviceIndependentSize().toSize(); + const bool arrowVisible = hovered || expanded; + + const QString sessionName = idx.data(Qt::DisplayRole).toString(); + + const int x = bgR.x(); + const int y = bgR.y(); + + const int numberX = x + s(HPaddingXs); + const int iconX = numberX + shortcutNumberWidth + s(HGapXs); + const int arrowX = bgR.right() - s(HPaddingXs) - arrowS.width(); + const QRect arrowHoverR(arrowX - s(HGapXs) + 1, y, + s(HGapXs) + arrowS.width() + s(HPaddingXs), hdR.height()); + const int textX = withIcon() ? iconX + iconS.width() + s(HGapXs) : iconX; + const int iconY = y + (hdR.height() - iconS.height()) / 2; + const int arrowY = y + (hdR.height() - arrowS.height()) / 2; + + { + drawBackgroundRect(painter, bgR, hovered); + } if (idx.row() < 9) { - painter->setPen(foregroundSecondaryColor); - painter->setFont(sizedFont(10, option.widget)); - painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1)); + painter->setPen(shortcutNumberTF.color()); + painter->setFont(shortcutNumberTF.font()); + const QRect numberR(numberX, y, shortcutNumberWidth, hdR.height()); + const QString numberString = QString::number(idx.row() + 1); + painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString); + } + if (withIcon()) { + painter->drawPixmap(iconX, iconY, icon()); + } + { + const bool isLastSession = idx.data(SessionModel::LastSessionRole).toBool(); + const bool isActiveSession = idx.data(SessionModel::ActiveSessionRole).toBool(); + const bool isDefaultVirgin = SessionManager::isDefaultVirgin(); + + const int sessionNameWidth = hdR.right() + - (arrowVisible ? arrowHoverR.width(): s(HPaddingXs)) + - textX; + const int sessionNameHeight = sessionNameTF.lineHeight(); + const int sessionNameY = y + (hdR.height() - sessionNameHeight) / 2; + const QRect sessionNameR(textX, sessionNameY, sessionNameWidth, sessionNameHeight); + + QString fullSessionName = sessionName; + if (isLastSession && isDefaultVirgin) + fullSessionName = Tr::tr("%1 (last session)").arg(fullSessionName); + if (isActiveSession && !isDefaultVirgin) + fullSessionName = Tr::tr("%1 (current session)").arg(fullSessionName); + const QRect switchR(x, y, hdR.width() - arrowHoverR.width(), + hdR.height() + s(VGapL)); + const bool switchActive = switchR.contains(mousePos); + painter->setPen(sessionNameTF.color()); + painter->setFont(sessionNameTF.font(switchActive)); + const QString fullSessionNameElided = painter->fontMetrics().elidedText( + fullSessionName, Qt::ElideRight, sessionNameWidth); + painter->drawText(sessionNameR, sessionNameTF.drawTextFlags, + fullSessionNameElided); + if (switchActive) + m_activeSwitchToRect = switchR; + } + if (arrowVisible) { + if (arrowHoverR.adjusted(0, 0, 0, s(VGapL)).contains(mousePos)) { + m_activeExpandRect = arrowHoverR; + } else { + painter->save(); + painter->setClipRect(arrowHoverR); + drawBackgroundRect(painter, bgR, false); + painter->restore(); + } + static const QPixmap arrowDown = + QPixmap::fromImage(arrow.toImage().mirrored(false, true)); + painter->drawPixmap(arrowX, arrowY, expanded ? arrowDown : arrow); } - const bool isLastSession = idx.data(SessionModel::LastSessionRole).toBool(); - const bool isActiveSession = idx.data(SessionModel::ActiveSessionRole).toBool(); - const bool isDefaultVirgin = SessionManager::isDefaultVirgin(); - - QString fullSessionName = sessionName; - if (isLastSession && isDefaultVirgin) - fullSessionName = Tr::tr("%1 (last session)").arg(fullSessionName); - if (isActiveSession && !isDefaultVirgin) - fullSessionName = Tr::tr("%1 (current session)").arg(fullSessionName); - - const QRect switchRect = QRect(x, y, rc.width() - SESSION_ARROW_RECT_WIDTH, SESSION_LINE_HEIGHT); - const bool switchActive = switchRect.contains(mousePos); - const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - 6; - const int sessionNameTextSpace = - textSpace -(hovered || expanded ? SESSION_ARROW_RECT_WIDTH : 0); - painter->setPen(linkColor); - painter->setFont(sizedFont(13, option.widget, switchActive)); - const QString fullSessionNameElided = painter->fontMetrics().elidedText( - fullSessionName, Qt::ElideRight, sessionNameTextSpace); - painter->drawText(x1, firstBase, fullSessionNameElided); - if (switchActive) - m_activeSwitchToRect = switchRect; - + int yy = hdR.bottom(); if (expanded) { - painter->setPen(textColor); - painter->setFont(sizedFont(12, option.widget)); + const QFont projectNameFont = sessionProjetNameTF.font(); + const QFontMetrics projectNameFm(projectNameFont); + const int projectNameLineHeight = sessionProjetNameTF.lineHeight(); + const QFont projectPathFont = projectPathTF.font(); + const QFontMetrics projectPathFm(projectPathFont); + const int projectPathLineHeight = projectPathTF.lineHeight(); + const int textWidth = bgR.right() - s(HPaddingXs) - textX; + const FilePaths projects = ProjectManager::projectsForSessionName(sessionName); - int yy = firstBase + SESSION_LINE_HEIGHT - 3; - QFontMetrics fm(option.widget->font()); for (const FilePath &projectPath : projects) { - // Project name. - QString completeBase = projectPath.completeBaseName(); - painter->setPen(textColor); - painter->drawText(x1, yy, fm.elidedText(completeBase, Qt::ElideMiddle, textSpace)); - yy += 18; - - // Project path. - const QString displayPath = - projectPath.osType() == OsTypeWindows ? projectPath.displayName() - : projectPath.withTildeHomePath(); - painter->setPen(foregroundPrimaryColor); - painter->drawText(x1, yy, fm.elidedText(displayPath, Qt::ElideMiddle, textSpace)); - yy += 22; + yy += s(VPaddingXs); + { + painter->setFont(projectNameFont); + painter->setPen(sessionProjetNameTF.color()); + const QRect projectNameR(textX, yy, textWidth, projectNameLineHeight); + const QString projectNameElided = + projectNameFm.elidedText(projectPath.completeBaseName(), Qt::ElideMiddle, + textWidth); + painter->drawText(projectNameR, sessionProjetNameTF.drawTextFlags, + projectNameElided); + yy += projectNameLineHeight; + yy += s(ExPaddingGapS); + } + { + const QString displayPath = + projectPath.osType() == OsTypeWindows ? projectPath.displayName() + : projectPath.withTildeHomePath(); + painter->setFont(projectPathFont); + painter->setPen(projectPathTF.color()); + const QRect projectPathR(textX, yy, textWidth, projectPathLineHeight); + const QString projectPathElided = + projectPathFm.elidedText(displayPath, Qt::ElideMiddle, textWidth); + painter->drawText(projectPathR, projectPathTF.drawTextFlags, + projectPathElided); + yy += projectPathLineHeight; + } + yy += s(VPaddingXs); } + yy += s(VGapXs); - yy += 3; - int xx = x1; const QStringList actions = { Tr::tr("Clone"), Tr::tr("Rename"), - Tr::tr("Delete") + Tr::tr("Delete"), }; - for (int i = 0; i < 3; ++i) { + + const QFont actionFont = actionTF.font(); + const QFontMetrics actionFm(actionTF.font()); + + const int gapWidth = s(HGapXs) + actionSepWidth + s(HGapXs); + int actionsTotalWidth = gapWidth * int(actions.count() - 1); // dividers + const auto textWidths = Utils::transform(actions, [&] (const QString &action) { + const int width = actionFm.horizontalAdvance(action); + actionsTotalWidth += s(ExPaddingGapM) + width + s(ExPaddingGapM); + return width; + }); + + const int buttonHeight = this->actionButtonHeight(); + int xx = (bgR.width() - actionsTotalWidth) / 2; + for (int i = 0; i < actions.count(); ++i) { const QString &action = actions.at(i); - const int ww = fm.horizontalAdvance(action); - const int spacing = 7; // Between action link and separator line - const QRect actionRect = - QRect(xx, yy - 10, ww, 15).adjusted(-spacing, -spacing, spacing, spacing); - const bool isForcedDisabled = (i != 0 && sessionName == "default"); - const bool isActive = actionRect.contains(mousePos) && !isForcedDisabled; - painter->setPen(isForcedDisabled ? disabledLinkColor : linkColor); - painter->setFont(sizedFont(12, option.widget, isActive)); - painter->drawText(xx, yy, action); - if (i < 2) { - xx += ww + 2 * spacing; - int pp = xx - spacing; - painter->setPen(textColor); - painter->drawLine(pp, yy - 10, pp, yy); + const int ww = textWidths.at(i); + const QRect actionR(xx, yy, s(ExPaddingGapM) + ww + s(ExPaddingGapM), buttonHeight); + const bool isDisabled = i > 0 && SessionManager::isDefaultSession(sessionName); + const bool isActive = actionR.adjusted(-s(VPaddingXs), 0, s(VPaddingXs) + 1, 0) + .contains(mousePos) && !isDisabled; + if (isActive) { + WelcomePageHelpers::drawCardBackground(painter, actionR, Qt::transparent, + themeColor(Theme::Token_Text_Muted)); + m_activeActionRects[i] = actionR; } - if (isActive) - m_activeActionRects[i] = actionRect; + painter->setFont(actionFont); + painter->setPen((isDisabled ? actionDisabledTF : actionTF).color()); + const QRect actionTextR = actionR.adjusted(0, 0, 0, -1); + painter->drawText(actionTextR, actionTF.drawTextFlags, action); + xx += actionR.width(); + if (i < actions.count() - 1) { + const QRect dividerR(xx + s(HGapXs), yy, actionSepWidth, buttonHeight); + painter->fillRect(dividerR, themeColor(Theme::Token_Text_Muted)); + } + xx += gapWidth; } + yy += buttonHeight; + yy += s(VGapXs); } + QTC_CHECK(option.rect.bottom() == yy + itemSpacing()); + } + + static int headerHeight() + { + const int paddingsHeight = s(VPaddingXs + VPaddingXs); + const int heightForSessionName = sessionNameTF.lineHeight() + paddingsHeight; + const int heightForIcon = + withIcon() ? int(icon().deviceIndependentSize().height()) + paddingsHeight : 0; + return qMax(heightForSessionName, heightForIcon); } QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &idx) const final { - int h = SESSION_LINE_HEIGHT; - QString sessionName = idx.data(Qt::DisplayRole).toString(); - if (m_expandedSessions.contains(sessionName)) { + int h = headerHeight(); + if (expanded(idx)) { + const QString sessionName = idx.data(Qt::DisplayRole).toString(); const FilePaths projects = ProjectManager::projectsForSessionName(sessionName); - h += projects.size() * 40 + LINK_HEIGHT - 6; + const int projectEntryHeight = + s(VPaddingXs) + + projectNameTF.lineHeight() + + s(ExPaddingGapS) + + projectPathTF.lineHeight() + + s(VPaddingXs); + h += projects.size() * projectEntryHeight + + s(VGapXs) + + actionButtonHeight() + + s(VGapXs); } - return QSize(380, h + ItemGap); + return QSize(-1, h + itemSpacing()); } bool editorEvent(QEvent *ev, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &idx) final + const QStyleOptionViewItem &, const QModelIndex &idx) final { if (ev->type() == QEvent::MouseButtonRelease) { const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev); const Qt::MouseButtons button = mouseEvent->button(); const QPoint pos = static_cast<QMouseEvent *>(ev)->pos(); - const QRect rc(option.rect.right() - SESSION_ARROW_RECT_WIDTH, option.rect.top(), - SESSION_ARROW_RECT_WIDTH, SESSION_LINE_HEIGHT); const QString sessionName = idx.data(Qt::DisplayRole).toString(); - if (rc.contains(pos) || button == Qt::RightButton) { + if (m_activeExpandRect.contains(pos) || button == Qt::RightButton) { // The expand/collapse "button". if (m_expandedSessions.contains(sessionName)) m_expandedSessions.removeOne(sessionName); @@ -421,23 +600,15 @@ public: } if (ev->type() == QEvent::MouseMove) { emit model->layoutChanged({QPersistentModelIndex(idx)}); // Somewhat brutish. - //update(option.rect); return false; } return false; } private: - const QColor hoverColor = themeColor(Theme::Welcome_HoverColor); - const QColor textColor = themeColor(Theme::Welcome_TextColor); - const QColor linkColor = themeColor(Theme::Welcome_LinkColor); - const QColor disabledLinkColor = themeColor(Theme::Welcome_DisabledLinkColor); - const QColor backgroundPrimaryColor = themeColor(Theme::Welcome_BackgroundPrimaryColor); - const QColor foregroundPrimaryColor = themeColor(Theme::Welcome_ForegroundPrimaryColor); - const QColor foregroundSecondaryColor = themeColor(Theme::Welcome_ForegroundSecondaryColor); - QStringList m_expandedSessions; + mutable QRect m_activeExpandRect; mutable QRect m_activeSwitchToRect; mutable QRect m_activeActionRects[3]; }; @@ -453,58 +624,89 @@ class ProjectDelegate : public BaseDelegate public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final { - QRect rc = option.rect; - - const bool hovered = option.widget->isActiveWindow() && option.state & QStyle::State_MouseOver; - const QRect bgRect = rc.adjusted(0, 0, -ItemGap, -ItemGap); - painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor)); - painter->fillRect(bgRect, themeColor(hovered ? Theme::Welcome_HoverColor - : Theme::Welcome_BackgroundPrimaryColor)); - - const int x = rc.x(); - const int y = rc.y(); - const int firstBase = y + 18; - const int secondBase = firstBase + 19; - - static const QPixmap projectIcon = - pixmap("project", Theme::Welcome_ForegroundSecondaryColor); - painter->drawPixmap(x + 11, y + 6, projectIcon); - - QString projectName = idx.data(Qt::DisplayRole).toString(); - FilePath projectPath = FilePath::fromVariant(idx.data(ProjectModel::FilePathRole)); - - painter->setPen(themeColor(Theme::Welcome_ForegroundSecondaryColor)); - painter->setFont(sizedFont(10, option.widget)); - - if (idx.row() < 9) - painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1)); - - const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - ItemGap - 6; - - painter->setPen(themeColor(Theme::Welcome_LinkColor)); - painter->setFont(sizedFont(13, option.widget, hovered)); - const QString projectNameElided = - painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textSpace); - painter->drawText(x + TEXT_OFFSET_HORIZONTAL, firstBase, projectNameElided); - - painter->setPen(themeColor(Theme::Welcome_ForegroundPrimaryColor)); - painter->setFont(sizedFont(13, option.widget)); - const QString displayPath = - projectPath.osType() == OsTypeWindows ? projectPath.displayName() - : projectPath.withTildeHomePath(); - const QString displayPathElided = - painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textSpace); - painter->drawText(x + TEXT_OFFSET_HORIZONTAL, secondBase, displayPathElided); + // visible on withIcon() Extra margin right of project item + // | | + // +-------+-------+ +------+-----+ + // | | | | + // + // +------------+--------+--------+------+--------+-------------+------------+------------+ + // | | | | | | (VPaddingXs)| | | + // | | | | | +-------------+ | | + // | | | | | |<projectName>| | | + // | | | | | +-------------+ | | + // |(HPaddingXs)|<number>|(HGapXs)|<icon>|(HGapXs)| (VGapXs) |(HPaddingXs)|(HPaddingXs)| + // | |(16x16) | | | +-------------+ | | + // | | | | | |<projectPath>| | | + // | | | | | +-------------+ | | + // | | | | | | (VPaddingXs)| | | + // +------------+--------+--------+------+--------+-------------+------------+------------+ --+ + // | (VGapL) | +-- Gap between project items + // +--------------------------------------------------------------------------------------+ --+ + + const bool hovered = option.widget->isActiveWindow() + && option.state & QStyle::State_MouseOver; + + const QRect bgR = option.rect.adjusted(0, 0, -s(HPaddingXs), -itemSpacing()); + + static const QPixmap icon = pixmap("project", Theme::Token_Text_Muted); + const QSize iconS = icon.deviceIndependentSize().toSize(); + + const int x = bgR.x(); + const int numberX = x + s(HPaddingXs); + const int iconX = numberX + shortcutNumberWidth + s(HGapXs); + const int iconWidth = iconS.width(); + const int textX = withIcon() ? iconX + iconWidth + s(HGapXs) : iconX; + const int textWidth = bgR.width() - s(HPaddingXs) - textX; + + const int y = bgR.y(); + const int iconHeight = iconS.height(); + const int iconY = y + (bgR.height() - iconHeight) / 2; + const int projectNameY = y + s(VPaddingXs); + const QRect projectNameR(textX, projectNameY, textWidth, projectNameTF.lineHeight()); + const int projectPathY = projectNameY + projectNameR.height() + s(VGapXs); + const QRect projectPathR(textX, projectPathY, textWidth, projectPathTF.lineHeight()); + + QTC_CHECK(option.rect.bottom() == projectPathR.bottom() + s(VPaddingXs) + itemSpacing()); + + { + drawBackgroundRect(painter, bgR, hovered); + } + if (idx.row() < 9) { + painter->setPen(shortcutNumberTF.color()); + painter->setFont(shortcutNumberTF.font()); + const QRect numberR(numberX, y, shortcutNumberWidth, bgR.height()); + const QString numberString = QString::number(idx.row() + 1); + painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString); + } + if (withIcon()) { + painter->drawPixmap(iconX, iconY, icon); + } + { + painter->setPen(projectNameTF.color()); + painter->setFont(projectNameTF.font(hovered)); + const QString projectName = idx.data(Qt::DisplayRole).toString(); + const QString projectNameElided = + painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textWidth); + painter->drawText(projectNameR, projectNameTF.drawTextFlags, projectNameElided); + } + { + painter->setPen(projectPathTF.color()); + painter->setFont(projectPathTF.font()); + const FilePath projectPath = + FilePath::fromVariant(idx.data(ProjectModel::FilePathRole)); + const QString displayPath = + projectPath.osType() == OsTypeWindows ? projectPath.displayName() + : projectPath.withTildeHomePath(); + const QString displayPathElided = + painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textWidth); + painter->drawText(projectPathR, projectPathTF.drawTextFlags, displayPathElided); + } } - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const final + QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option, + [[maybe_unused]] const QModelIndex &idx) const override { - QString projectName = idx.data(Qt::DisplayRole).toString(); - QString projectPath = idx.data(ProjectModel::FilePathRole).toString(); - QFontMetrics fm(sizedFont(13, option.widget)); - int width = std::max(fm.horizontalAdvance(projectName), - fm.horizontalAdvance(projectPath)) + TEXT_OFFSET_HORIZONTAL; - return QSize(width, 47 + ItemGap); + return QSize(-1, itemHeight() + itemSpacing()); } bool editorEvent(QEvent *ev, QAbstractItemModel *model, @@ -541,6 +743,18 @@ public: } return false; } + +private: + static int itemHeight() + { + const int height = + s(VPaddingXs) + + projectNameTF.lineHeight() + + s(VGapXs) + + projectPathTF.lineHeight() + + s(VPaddingXs); + return height; + } }; class TreeView : public QTreeView @@ -559,10 +773,7 @@ public: setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); setFocusPolicy(Qt::NoFocus); - - QPalette pal; - pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor)); - viewport()->setPalette(pal); + setBackgroundColor(viewport(), Theme::Token_Background_Default); } }; @@ -577,54 +788,63 @@ public: if (!projectWelcomePage->m_projectModel) projectWelcomePage->m_projectModel = new ProjectModel(this); - auto manageSessionsButton = new WelcomePageButton(this); - manageSessionsButton->setText(Tr::tr("Manage...")); - manageSessionsButton->setWithAccentColor(true); - manageSessionsButton->setOnClicked([] { SessionManager::showSessionManager(); }); - - auto sessionsLabel = new QLabel(this); - sessionsLabel->setText(Tr::tr("Sessions")); - - auto recentProjectsLabel = new QLabel(this); - recentProjectsLabel->setText(Tr::tr("Projects")); - - auto sessionsList = new TreeView(this, "Sessions"); - sessionsList->setModel(projectWelcomePage->m_sessionModel); - sessionsList->header()->setSectionHidden(1, true); // The "last modified" column. - sessionsList->setItemDelegate(&m_sessionDelegate); - sessionsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - - auto projectsList = new TreeView(this, "Recent Projects"); - projectsList->setUniformRowHeights(true); - projectsList->setModel(projectWelcomePage->m_projectModel); - projectsList->setItemDelegate(&m_projectDelegate); - projectsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - - auto sessionHeader = panelBar(this); - auto hbox11 = new QHBoxLayout(sessionHeader); - hbox11->setContentsMargins(12, 0, 0, 0); - hbox11->addWidget(sessionsLabel); - hbox11->addStretch(1); - hbox11->addWidget(manageSessionsButton); - - auto projectsHeader = panelBar(this); - auto hbox21 = new QHBoxLayout(projectsHeader); - hbox21->setContentsMargins(hbox11->contentsMargins()); - hbox21->addWidget(recentProjectsLabel); - - auto grid = new QGridLayout(this); - grid->setContentsMargins(0, 0, 0, ItemGap); - grid->setHorizontalSpacing(0); - grid->setVerticalSpacing(ItemGap); - grid->addWidget(panelBar(this), 0, 0); - grid->addWidget(sessionHeader, 0, 1); - grid->addWidget(sessionsList, 1, 1); - grid->addWidget(panelBar(this), 0, 2); - grid->setColumnStretch(1, 9); - grid->setColumnMinimumWidth(1, 200); - grid->addWidget(projectsHeader, 0, 3); - grid->addWidget(projectsList, 1, 3); - grid->setColumnStretch(3, 20); + using namespace Layouting; + + auto sessions = new QWidget; + { + auto sessionsLabel = new Label(Tr::tr("Sessions"), Label::Primary); + auto manageSessionsButton = new Button(Tr::tr("Manage..."), Button::MediumSecondary); + auto sessionsList = new TreeView(this, "Sessions"); + sessionsList->setModel(projectWelcomePage->m_sessionModel); + sessionsList->header()->setSectionHidden(1, true); // The "last modified" column. + sessionsList->setItemDelegate(&m_sessionDelegate); + sessionsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + QSizePolicy sessionsSp(QSizePolicy::Expanding, QSizePolicy::Expanding); + sessionsSp.setHorizontalStretch(3); + sessions->setSizePolicy(sessionsSp); + Column { + Row { + sessionsLabel, + st, + manageSessionsButton, + customMargin({HPaddingS, 0, sessionScrollBarGap, 0}), + }, + sessionsList, + spacing(ExPaddingGapL), + customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}), + }.attachTo(sessions); + connect(manageSessionsButton, &Button::clicked, + this, &SessionManager::showSessionManager); + } + + auto projects = new QWidget; + { + auto projectsLabel = new Label(Tr::tr("Projects"), Label::Primary); + auto projectsList = new TreeView(this, "Recent Projects"); + projectsList->setUniformRowHeights(true); + projectsList->setModel(projectWelcomePage->m_projectModel); + projectsList->setItemDelegate(&m_projectDelegate); + projectsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + QSizePolicy projectsSP(QSizePolicy::Expanding, QSizePolicy::Expanding); + projectsSP.setHorizontalStretch(5); + projects->setSizePolicy(projectsSP); + Column { + Row { + projectsLabel, + customMargin({HPaddingS, 0, 0, 0}), + }, + projectsList, + spacing(ExPaddingGapL), + customMargin({ExVPaddingGapXl - sessionScrollBarGap, ExVPaddingGapXl, 0, 0}), + }.attachTo(projects); + } + + Row { + sessions, + projects, + spacing(0), + noMargin(), + }.attachTo(this); } SessionDelegate m_sessionDelegate; diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp index 18aa6cc381..e81535acb4 100644 --- a/src/plugins/qtsupport/exampleslistmodel.cpp +++ b/src/plugins/qtsupport/exampleslistmodel.cpp @@ -250,7 +250,7 @@ static QPixmap fetchPixmapAndUpdatePixmapCache(const QString &url) const int dpr = qApp->devicePixelRatio(); // boundedTo -> don't scale thumbnails up const QSize scaledSize = - WelcomePageHelpers::GridItemImageSize.boundedTo(img.size()) * dpr; + WelcomePageHelpers::WelcomeThumbnailSize.boundedTo(img.size()) * dpr; const QImage scaled = img.isNull() ? img : img.scaled(scaledSize, Qt::KeepAspectRatio, diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp index f5b5c0ac49..f02a433fbe 100644 --- a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp +++ b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp @@ -21,13 +21,13 @@ #include <utils/algorithm.h> #include <utils/fileutils.h> +#include <utils/layoutbuilder.h> #include <utils/pathchooser.h> #include <utils/qtcassert.h> #include <utils/stylehelper.h> #include <utils/theme/theme.h> #include <utils/winutils.h> -#include <QComboBox> #include <QDesktopServices> #include <QDialogButtonBox> #include <QElapsedTimer> @@ -249,8 +249,9 @@ protected: painter->setFont(option.font); painter->setCompositionMode(QPainter::CompositionMode_Difference); painter->setPen(Qt::white); - painter->drawText(currentPixmapRect.translated(0, -WelcomePageHelpers::ItemGap), - exampleItem->videoLength, Qt::AlignBottom | Qt::AlignHCenter); + painter->drawText( + currentPixmapRect.translated(0, -StyleHelper::SpacingTokens::VPaddingXxs), + exampleItem->videoLength, Qt::AlignBottom | Qt::AlignHCenter); painter->restore(); static const QPixmap playOverlay = StyleHelper::dpiSpecificImageFile(":/qtsupport/images/icons/playoverlay.png"); @@ -274,29 +275,25 @@ public: { m_exampleDelegate.setShowExamples(isExamples); - auto searchBox = new SearchBox(this); - m_searcher = searchBox->m_lineEdit; + using namespace StyleHelper::SpacingTokens; - auto grid = new QGridLayout(this); - grid->setContentsMargins(0, 0, 0, WelcomePageHelpers::ItemGap); - grid->setHorizontalSpacing(0); - grid->setVerticalSpacing(WelcomePageHelpers::ItemGap); + using namespace Layouting; + Row titleRow { + customMargin({0, 0, ExVPaddingGapXl, 0}), + spacing(ExVPaddingGapXl), + }; - auto searchBar = WelcomePageHelpers::panelBar(this); - auto hbox = new QHBoxLayout(searchBar); - hbox->setContentsMargins(0, 0, 0, 0); + m_searcher = new SearchBox; if (m_isExamples) { m_searcher->setPlaceholderText(Tr::tr("Search in Examples...")); - auto exampleSetSelector = new QComboBox(this); - QPalette pal = exampleSetSelector->palette(); - // for macOS dark mode - pal.setColor(QPalette::Text, Utils::creatorTheme()->color(Theme::Welcome_TextColor)); - exampleSetSelector->setPalette(pal); - exampleSetSelector->setMinimumWidth(Core::WelcomePageHelpers::GridItemWidth); - exampleSetSelector->setMaximumWidth(Core::WelcomePageHelpers::GridItemWidth); + auto exampleSetSelector = new ComboBox; + exampleSetSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); + exampleSetSelector->setMinimumWidth(ListItemDelegate::itemSize().width() + - ExVPaddingGapXl); exampleSetSelector->setModel(s_exampleSetModel); exampleSetSelector->setCurrentIndex(s_exampleSetModel->selectedExampleSet()); + titleRow.addItem(exampleSetSelector); connect(exampleSetSelector, &QComboBox::activated, s_exampleSetModel, @@ -305,23 +302,23 @@ public: &ExampleSetModel::selectedExampleSetChanged, exampleSetSelector, &QComboBox::setCurrentIndex); - - hbox->setSpacing(Core::WelcomePageHelpers::HSpacing); - hbox->addWidget(exampleSetSelector); } else { m_searcher->setPlaceholderText(Tr::tr("Search in Tutorials...")); } - hbox->addWidget(searchBox); - grid->addWidget(WelcomePageHelpers::panelBar(this), 0, 0); - grid->addWidget(searchBar, 0, 1); - grid->addWidget(WelcomePageHelpers::panelBar(this), 0, 2); + titleRow.addItem(m_searcher); - auto gridView = new SectionedGridView(this); + auto gridView = new SectionedGridView; m_viewController = new ExamplesViewController(s_exampleSetModel, gridView, m_searcher, isExamples, this); gridView->setItemDelegate(&m_exampleDelegate); - grid->addWidget(gridView, 1, 1, 1, 2); + + Column { + titleRow, + gridView, + spacing(ExVPaddingGapXl), + customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}), + }.attachTo(this); connect(&m_exampleDelegate, &ExampleDelegate::tagClicked, this, &ExamplesPageWidget::onTagClicked); diff --git a/src/plugins/welcome/images/expandarrow.png b/src/plugins/welcome/images/expandarrow.png Binary files differdeleted file mode 100644 index 1c80e4eaac..0000000000 --- a/src/plugins/welcome/images/expandarrow.png +++ /dev/null diff --git a/src/plugins/welcome/images/expandarrow@2x.png b/src/plugins/welcome/images/expandarrow@2x.png Binary files differdeleted file mode 100644 index 30b7d23aed..0000000000 --- a/src/plugins/welcome/images/expandarrow@2x.png +++ /dev/null diff --git a/src/plugins/welcome/images/link.png b/src/plugins/welcome/images/link.png Binary files differnew file mode 100644 index 0000000000..3fff0753bc --- /dev/null +++ b/src/plugins/welcome/images/link.png diff --git a/src/plugins/welcome/images/link@2x.png b/src/plugins/welcome/images/link@2x.png Binary files differnew file mode 100644 index 0000000000..be4f2789ae --- /dev/null +++ b/src/plugins/welcome/images/link@2x.png diff --git a/src/plugins/welcome/images/project.png b/src/plugins/welcome/images/project.png Binary files differindex 1fd5370e7e..30862beb28 100644 --- a/src/plugins/welcome/images/project.png +++ b/src/plugins/welcome/images/project.png diff --git a/src/plugins/welcome/images/project@2x.png b/src/plugins/welcome/images/project@2x.png Binary files differindex df357dfa95..c5cc11155b 100644 --- a/src/plugins/welcome/images/project@2x.png +++ b/src/plugins/welcome/images/project@2x.png diff --git a/src/plugins/welcome/images/session.png b/src/plugins/welcome/images/session.png Binary files differindex 7a1e2c51d2..aa14f11de8 100644 --- a/src/plugins/welcome/images/session.png +++ b/src/plugins/welcome/images/session.png diff --git a/src/plugins/welcome/images/session@2x.png b/src/plugins/welcome/images/session@2x.png Binary files differindex d146a1a3e9..c53cf12bfa 100644 --- a/src/plugins/welcome/images/session@2x.png +++ b/src/plugins/welcome/images/session@2x.png diff --git a/src/plugins/welcome/welcome.qrc b/src/plugins/welcome/welcome.qrc index 6734370f95..3158be8568 100644 --- a/src/plugins/welcome/welcome.qrc +++ b/src/plugins/welcome/welcome.qrc @@ -8,8 +8,8 @@ <file>images/project@2x.png</file> <file>images/session.png</file> <file>images/session@2x.png</file> - <file>images/expandarrow.png</file> - <file>images/expandarrow@2x.png</file> <file>images/border.png</file> + <file>images/link.png</file> + <file>images/link@2x.png</file> </qresource> </RCC> diff --git a/src/plugins/welcome/welcomeplugin.cpp b/src/plugins/welcome/welcomeplugin.cpp index 0590823ea6..2513ebaa10 100644 --- a/src/plugins/welcome/welcomeplugin.cpp +++ b/src/plugins/welcome/welcomeplugin.cpp @@ -22,12 +22,14 @@ #include <utils/fileutils.h> #include <utils/hostosinfo.h> #include <utils/icon.h> +#include <utils/layoutbuilder.h> #include <utils/qtcassert.h> #include <utils/styledbar.h> #include <utils/stylehelper.h> #include <utils/theme/theme.h> #include <utils/treemodel.h> +#include <QButtonGroup> #include <QDesktopServices> #include <QGuiApplication> #include <QLabel> @@ -42,31 +44,15 @@ using namespace Core; using namespace Core::WelcomePageHelpers; using namespace ExtensionSystem; using namespace Utils; +using namespace StyleHelper::SpacingTokens; namespace Welcome { namespace Internal { class TopArea; class SideArea; -class BottomArea; const char currentPageSettingsKeyC[] = "Welcome2Tab"; -constexpr int buttonSpacing = 16; - -static QColor themeColor(Theme::Color role) -{ - return Utils::creatorTheme()->color(role); -} - -static void addWeakVerticalSpacerToLayout(QVBoxLayout *layout, int maximumSize) -{ - auto weakSpacer = new QWidget; - weakSpacer->setMaximumHeight(maximumSize); - weakSpacer->setMinimumHeight(buttonSpacing); - weakSpacer->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); - layout->addWidget(weakSpacer); - layout->setStretchFactor(weakSpacer, 1); -} class WelcomeMode : public IMode { @@ -85,9 +71,9 @@ private: QStackedWidget *m_pageStack; TopArea *m_topArea; SideArea *m_sideArea; - BottomArea *m_bottomArea; QList<IWelcomePage *> m_pluginList; - QList<WelcomePageButton *> m_pageButtons; + QList<QPushButton *> m_pageButtons; + QButtonGroup *m_buttonGroup; Id m_activePage; Id m_defaultPage; }; @@ -140,55 +126,43 @@ public: TopArea(QWidget *parent = nullptr) : QWidget(parent) { - setAutoFillBackground(true); - setMinimumHeight(11); // For compact state - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - setPalette(themeColor(Theme::Welcome_BackgroundPrimaryColor)); - - m_title = new QWidget; + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); - auto hbox = new QHBoxLayout(m_title); - hbox->setSpacing(0); - hbox->setContentsMargins(HSpacing - 5, 2, 0, 2); + constexpr TextFormat welcomeTF {Theme::Token_Text_Default, StyleHelper::UiElementH2}; + auto ideIconLabel = new QLabel; { - auto ideIconLabel = new QLabel; const QPixmap logo = Core::Icons::QTCREATORLOGO_BIG.pixmap(); - ideIconLabel->setPixmap(logo.scaled(logo.size() * 0.6, Qt::IgnoreAspectRatio, - Qt::SmoothTransformation)); - hbox->addWidget(ideIconLabel, 0); - - hbox->addSpacing(16); - - const QFont welcomeFont = StyleHelper::uiFont(StyleHelper::UiElementH1); - - auto welcomeLabel = new QLabel("Welcome to"); - welcomeLabel->setFont(welcomeFont); - hbox->addWidget(welcomeLabel, 0); - - hbox->addSpacing(8); + const int size = logo.width(); + const QRect cropR = size == 128 ? QRect(9, 22, 110, 84) : QRect(17, 45, 222, 166); + const QPixmap croppedLogo = logo.copy(cropR); + const int lineHeight = welcomeTF.lineHeight(); + const QPixmap scaledCroppedLogo = + croppedLogo.scaledToHeight((lineHeight - 12) * devicePixelRatioF(), + Qt::SmoothTransformation); + ideIconLabel->setPixmap(scaledCroppedLogo); + ideIconLabel->setFixedHeight(lineHeight); + } - auto ideNameLabel = new QLabel(QGuiApplication::applicationDisplayName()); - ideNameLabel->setFont(welcomeFont); - ideNameLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + auto welcomeLabel = new QLabel(Tr::tr("Welcome to %1") + .arg(QGuiApplication::applicationDisplayName())); + { + welcomeLabel->setFont(welcomeTF.font()); QPalette pal = palette(); - pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_AccentColor)); - ideNameLabel->setPalette(pal); - hbox->addWidget(ideNameLabel, 1); + pal.setColor(QPalette::WindowText, welcomeTF.color()); + welcomeLabel->setPalette(pal); } - auto mainLayout = new QHBoxLayout(this); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->addWidget(m_title); - } + using namespace Layouting; - void setCompact(bool compact) - { - m_title->setVisible(!compact); + Row { + ideIconLabel, + welcomeLabel, + st, + spacing(ExVPaddingGapXl), + customMargin({HPaddingM, VPaddingM, HPaddingM, VPaddingM}), + }.attachTo(this); } - -private: - QWidget *m_title; }; class SideArea : public QScrollArea @@ -201,114 +175,103 @@ public: { setWidgetResizable(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameShape(QFrame::NoFrame); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + + using namespace Layouting; - auto mainWidget = new QWidget(this); - mainWidget->setAutoFillBackground(true); - mainWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - mainWidget->setPalette(themeColor(Theme::Welcome_BackgroundPrimaryColor)); + Column mainLayout { + spacing(0), + customMargin({ExVPaddingGapXl, 0, ExVPaddingGapXl, 0}), + }; - auto vbox = new QVBoxLayout(mainWidget); - vbox->setSpacing(0); - vbox->setContentsMargins(HSpacing, 0, HSpacing, 0); + m_essentials = new QWidget; + Column essentials { + spacing(0), + noMargin(), + }; { - auto projectVBox = new QVBoxLayout; - projectVBox->setSpacing(buttonSpacing); - auto newButton = new WelcomePageButton(mainWidget); - newButton->setText(Tr::tr("Create Project...")); - newButton->setWithAccentColor(true); - newButton->setOnClicked([] { - QAction *openAction = ActionManager::command(Core::Constants::NEW)->action(); - openAction->trigger(); - }); + auto newButton = new Button(Tr::tr("Create Project..."), Button::MediumPrimary); + auto openButton = new Button(Tr::tr("Open Project..."), Button::MediumSecondary); + + Column projectButtons { + newButton, + openButton, + spacing(ExPaddingGapL), + customMargin({0, ExVPaddingGapXl, 0, ExVPaddingGapXl}), + }; + + essentials.addItem(projectButtons); - auto openButton = new WelcomePageButton(mainWidget); - openButton->setText(Tr::tr("Open Project...")); - openButton->setWithAccentColor(true); - openButton->setOnClicked([] { + connect(openButton, &Button::clicked, this, [] { QAction *openAction = ActionManager::command(Core::Constants::OPEN)->action(); openAction->trigger(); }); - - projectVBox->addWidget(newButton); - projectVBox->addWidget(openButton); - vbox->addItem(projectVBox); + connect(newButton, &Button::clicked, this, [] { + QAction *openAction = ActionManager::command(Core::Constants::NEW)->action(); + openAction->trigger(); + }); } - addWeakVerticalSpacerToLayout(vbox, 34); - { - auto newVBox = new QVBoxLayout; - newVBox->setSpacing(buttonSpacing / 3); - vbox->addItem(newVBox); - - auto newLabel = new QLabel(Tr::tr("New to Qt?"), mainWidget); - newLabel->setAlignment(Qt::AlignHCenter); - newVBox->addWidget(newLabel); - - auto getStartedButton = new WelcomePageButton(mainWidget); - getStartedButton->setText(Tr::tr("Get Started")); - getStartedButton->setOnClicked([] { - QDesktopServices::openUrl( - QString("qthelp://org.qt-project.qtcreator/doc/creator-getting-started.html")); - }); - newVBox->addWidget(getStartedButton); + auto l = m_pluginButtons = new QVBoxLayout; + l->setSpacing(VGapL); + l->setContentsMargins({}); + essentials.addItem(l); } - addWeakVerticalSpacerToLayout(vbox, 56); + essentials.attachTo(m_essentials); + mainLayout.addItem(m_essentials); + mainLayout.addItem(st); { - auto l = m_pluginButtons = new QVBoxLayout; - l->setSpacing(buttonSpacing); - vbox->addItem(l); + auto label = new Label(Tr::tr("Explore more"), Label::Secondary); + label->setContentsMargins(HPaddingXxs, 0, 0, 0); // Is indented in Figma design + + Column linksLayout { + label, + spacing(VGapS), + customMargin({0, VGapL, 0, ExVPaddingGapXl}), + }; + + const struct { + const QString label; + const QString url; + } links [] = + { + { Tr::tr("Get Started"), "qthelp://org.qt-project.qtcreator/doc/creator-getting-started.html" }, + { Tr::tr("Get Qt"), "https://www.qt.io/download" }, + { Tr::tr("Qt Account"), "https://account.qt.io" }, + { Tr::tr("Online Community"), "https://forum.qt.io" }, + { Tr::tr("Blogs"), "https://planet.qt.io" }, + { Tr::tr("User Guide"), "qthelp://org.qt-project.qtcreator/doc/index.html" }, + }; + for (auto &link : links) { + auto button = new Button(link.label, Button::SmallLink, this); + connect(button, &Button::clicked, this, [link]{ + QDesktopServices::openUrl(link.url);}); + button->setToolTip(link.url); + static const QPixmap icon = Icon({{":/welcome/images/link.png", + Theme::Token_Accent_Default}}, + Icon::Tint).pixmap(); + button->setPixmap(icon); + linksLayout.addItem(button); + } + + m_links = new QWidget; + linksLayout.attachTo(m_links); + mainLayout.addItem(m_links); } - vbox->addStretch(1); - + QWidget *mainWidget = mainLayout.emerge(); setWidget(mainWidget); } QVBoxLayout *m_pluginButtons = nullptr; -}; - -class BottomArea : public QWidget -{ - Q_OBJECT - -public: - BottomArea(QWidget *parent = nullptr) - : QWidget(parent) - { - setAutoFillBackground(true); - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - setPalette(themeColor(Theme::Welcome_BackgroundPrimaryColor)); - - auto hbox = new QHBoxLayout(this); - hbox->setSpacing(0); - hbox->setContentsMargins(0, 2 * ItemGap, HSpacing, 2 * ItemGap); - - const QList<QPair<QString, QString> > links { - { Tr::tr("Get Qt"), "https://www.qt.io/download" }, - { Tr::tr("Qt Account"), "https://account.qt.io" }, - { Tr::tr("Online Community"), "https://forum.qt.io" }, - { Tr::tr("Blogs"), "https://planet.qt.io" }, - { Tr::tr("User Guide"), "qthelp://org.qt-project.qtcreator/doc/index.html" }, - }; - for (const QPair<QString, QString> &link : links) { - auto button = new WelcomePageButton(this); - button->setSize(WelcomePageButton::SizeSmall); - button->setText(link.first); - button->setOnClicked([link]{ QDesktopServices::openUrl(link.second); }); - button->setWithAccentColor(true); - button->setMaximumWidth(220); - button->setToolTip(link.second); - if (hbox->count() > 0) - hbox->addStretch(1); - hbox->addWidget(button, 20); - } - } + QWidget *m_essentials = nullptr; + QWidget *m_links = nullptr; }; WelcomeMode::WelcomeMode() @@ -327,41 +290,51 @@ WelcomeMode::WelcomeMode() setContextHelp("Qt Creator Manual"); setContext(Context(Constants::C_WELCOME_MODE)); - QPalette palette = creatorTheme()->palette(); - palette.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundPrimaryColor)); - m_modeWidget = new ResizeSignallingWidget; - m_modeWidget->setPalette(palette); + setBackgroundColor(m_modeWidget, Theme::Token_Background_Default); connect(m_modeWidget, &ResizeSignallingWidget::resized, this, [this](const QSize &size, const QSize &) { - const bool hideSideArea = size.width() <= 750; - const bool hideBottomArea = size.width() <= 850; - const bool compactVertically = size.height() <= 530; - m_sideArea->setVisible(!hideSideArea); - m_bottomArea->setVisible(!(hideBottomArea || compactVertically)); - m_topArea->setCompact(compactVertically); + const QSize essentialsS = m_sideArea->m_essentials->size(); + const QSize linksS = m_sideArea->m_links->size(); + const QSize sideAreaS = m_sideArea->size(); + const QSize topAreaS = m_topArea->size(); + const QSize mainWindowS = ICore::mainWindow()->size(); + + const bool showSideArea = sideAreaS.width() < size.width() / 4; + const bool showTopArea = topAreaS.height() < mainWindowS.height() / 7.75; + const bool showLinks = + linksS.height() + essentialsS.height() < sideAreaS.height() && showTopArea; + + m_sideArea->m_links->setVisible(showLinks); + m_sideArea->setVisible(showSideArea); + m_topArea->setVisible(showTopArea); }); m_sideArea = new SideArea(m_modeWidget); + m_buttonGroup = new QButtonGroup(m_modeWidget); + m_buttonGroup->setExclusive(true); + m_pageStack = new QStackedWidget(m_modeWidget); - palette.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundSecondaryColor)); - m_pageStack->setPalette(palette); m_pageStack->setObjectName("WelcomeScreenStackedWidget"); m_pageStack->setAutoFillBackground(true); m_topArea = new TopArea; - m_bottomArea = new BottomArea; - - auto layout = new QGridLayout(m_modeWidget); - layout->addWidget(new StyledBar(m_modeWidget), 0, 0, 1, 2); - layout->addWidget(m_topArea, 1, 0, 1, 2); - layout->addWidget(m_sideArea, 2, 0, 2, 1); - layout->addWidget(m_pageStack, 2, 1, 1, 1); - layout->setColumnStretch(1, 10); - layout->addWidget(m_bottomArea, 3, 1, 1, 1); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); + + using namespace Layouting; + + Column { + new StyledBar, + m_topArea, + createRule(Qt::Horizontal), + Row { + m_sideArea, + createRule(Qt::Vertical), + m_pageStack, + }, + noMargin(), + spacing(0), + }.attachTo(m_modeWidget); setWidget(m_modeWidget); } @@ -402,11 +375,11 @@ void WelcomeMode::addPage(IWelcomePage *page) if (m_pluginList.at(idx)->priority() >= pagePriority) break; } - auto pageButton = new WelcomePageButton(m_sideArea->widget()); + auto pageButton = new Button(page->title(), Button::SmallList, m_sideArea->widget()); auto pageId = page->id(); pageButton->setText(page->title()); - pageButton->setActiveChecker([this, pageId] { return m_activePage == pageId; }); + m_buttonGroup->addButton(pageButton); m_pluginList.insert(idx, page); m_pageButtons.insert(idx, pageButton); @@ -417,6 +390,7 @@ void WelcomeMode::addPage(IWelcomePage *page) m_pageStack->insertWidget(idx, stackPage); connect(page, &QObject::destroyed, this, [this, page, stackPage, pageButton] { + m_buttonGroup->removeButton(pageButton); m_pluginList.removeOne(page); m_pageButtons.removeOne(pageButton); delete pageButton; @@ -426,13 +400,13 @@ void WelcomeMode::addPage(IWelcomePage *page) auto onClicked = [this, pageId, stackPage] { m_activePage = pageId; m_pageStack->setCurrentWidget(stackPage); - for (WelcomePageButton *pageButton : std::as_const(m_pageButtons)) - pageButton->recheckActive(); }; - pageButton->setOnClicked(onClicked); - if (pageId == m_activePage) + connect(pageButton, &Button::clicked, this, onClicked); + if (pageId == m_activePage) { onClicked(); + pageButton->setChecked(true); + } } } // namespace Internal diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg index 07452dc8c1..9ced69d319 100644 --- a/src/tools/icons/qtcreatoricons.svg +++ b/src/tools/icons/qtcreatoricons.svg @@ -668,6 +668,14 @@ x="0" y="0" xlink:href="#backgroundRect" + id="backgroundRect_18" + width="100%" + height="100%" + transform="matrix(1.125,0,0,1.125,0,-128.5)" /> + <use + x="0" + y="0" + xlink:href="#backgroundRect" id="backgroundRect_12" width="100%" height="100%" @@ -7022,51 +7030,78 @@ style="fill:none;stroke:#000000;stroke-width:1.42;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> </g> <g - transform="translate(-202,374)" + transform="translate(-209,274)" id="src/plugins/welcome/images/session"> - <rect - id="rect4886-1-1" - height="16" - width="16" - y="210" - x="250" - style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" /> + <use + x="0" + y="0" + xlink:href="#backgroundRect_24" + id="use9" + transform="translate(233,-210)" /> <path - id="path4942-1" - d="m 252,212 v 12 h 12 v -12 z m 4,10 v -8 l 5,3.999 z" - inkscape:connector-curvature="0" - style="fill:#000000;fill-opacity:1" /> + id="path8101" + style="fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round" + d="m 219,210 6,4 -6,4 z m -7,-5 h 18 v 18 h -18 z" /> </g> <g - transform="translate(-86,374)" - id="src/plugins/welcome/images/expandarrow"> - <rect - id="rect4961-9" - height="16" - width="16" - y="210" - x="150" - style="fill:#ffffff;fill-opacity:1" /> - <polygon - id="polygon4959-0" - points="162,215 158,220 154,215 " - style="fill:#000000;fill-opacity:1" - transform="translate(0,1)" /> + transform="translate(19,478)" + id="src/plugins/welcome/images/project"> + <use + x="0" + y="0" + xlink:href="#backgroundRect_24" + id="use9-5" + transform="translate(30,-414)" /> + <path + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round" + d="m 16,3 c 1,0 2,1 2,2 0,0 0,1 1,1 h 6 c 1,0 2,1 2,2 v 7 c 0,1 -1,2 -2,2 H 11 C 10,17 9,16 9,15 V 5 C 9,4 10,3 11,3 Z" + id="path2761-3" + sodipodi:nodetypes="cscccccccccc" /> </g> <g - transform="translate(-220,374)" - id="src/plugins/welcome/images/project"> - <rect - id="rect4950-8" - height="16" - width="16" - y="210" - x="300" - style="fill:#ffffff;fill-opacity:1" /> - <polygon - id="polygon4948-6" - points="308,215 314,215 314,223 302,223 302,213 306,213 " - style="fill:#000000;fill-opacity:1" /> + transform="translate(44,478)" + id="src/plugins/welcome/images/link"> + <use + x="0" + y="0" + xlink:href="#backgroundRect_24" + id="use14" + transform="translate(30,-414)" + width="100%" + height="100%" /> + <path + id="path9" + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round" + d="m 22.5,5.5 -10,10 M 16,5 h 7 v 7" /> + </g> + <g + transform="translate(-81,276)" + id="src/plugins/coreplugin/images/expandarrow"> + <use + x="0" + y="0" + xlink:href="#backgroundRect" + id="use15-0" + transform="translate(172,-228)" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" + d="m 159,214.5 5,5 5,-5" + id="path2687" + sodipodi:nodetypes="ccc" /> + </g> + <g + transform="translate(86,478)" + id="src/plugins/coreplugin/images/search"> + <use + x="0" + y="0" + xlink:href="#backgroundRect" + id="use15" + transform="translate(22,-430)" /> + <path + id="path10" + style="fill:none;stroke:#000000;stroke-width:1.5;stroke-linecap:round" + d="m 17,17 3.5,3.5 m -2.25,-7 A 4.75,4.75 0 0 1 13.5,18.25 4.75,4.75 0 0 1 8.75,13.5 4.75,4.75 0 0 1 13.5,8.75 4.75,4.75 0 0 1 18.25,13.5 Z" /> </g> <g id="src/libs/utils/images/member" diff --git a/tests/manual/widgets/uifonts/tst_manual_widgets_uifonts.cpp b/tests/manual/widgets/uifonts/tst_manual_widgets_uifonts.cpp index 786a78676f..4d324ec265 100644 --- a/tests/manual/widgets/uifonts/tst_manual_widgets_uifonts.cpp +++ b/tests/manual/widgets/uifonts/tst_manual_widgets_uifonts.cpp @@ -25,10 +25,14 @@ int main(int argc, char *argv[]) { StyleHelper::UiElementH5, "H5" }, { StyleHelper::UiElementH6, "H6" }, { StyleHelper::UiElementH6Capital, "H6 CAPITAL" }, + { StyleHelper::UiElementBody1, "Body-01" }, + { StyleHelper::UiElementBody2, "Body-02" }, + { StyleHelper::UiElementButtonMedium, "Button Medium" }, + { StyleHelper::UiElementButtonSmall, "Button Small" }, { StyleHelper::UiElementCaptionStrong, "Caption strong" }, { StyleHelper::UiElementCaption, "Caption" }, - { StyleHelper::UIElementIconStandard, "Icon Standard" }, - { StyleHelper::UIElementIconActive, "Icon Active" }, + { StyleHelper::UiElementIconStandard, "Icon Standard" }, + { StyleHelper::UiElementIconActive, "Icon Active" }, }; static const QString textSample("AaBbCcXxYyZz123"); |