diff --git a/CHANGES b/CHANGES index 3a6ebd0f3..ee66ee152 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,9 @@ This release contains contributions from (alphabetically by first name): Calamares). The example QML that is compiled into Calamares has been improved. To use your own QML, put files `calamares-sidebar.qml` or `calamares-navigation.qml` into the branding directory. + - The sidebar and navigation can now be placed on any side of the + main window. This is probably only useful for QML-based UIs. + See `branding.desc` for details. ## Modules ## - The *welcomeq* module has been improved with better layout and diff --git a/src/branding/default/branding.desc b/src/branding/default/branding.desc index 365af30e9..b6694d1f4 100644 --- a/src/branding/default/branding.desc +++ b/src/branding/default/branding.desc @@ -45,12 +45,25 @@ windowPlacement: center # - "widget" or unset, use traditional sidebar (logo, items) # - "none", hide it entirely # - "qml", use calamares-sidebar.qml from branding folder +# In addition, you **may** specify a side, separated by a comma, +# from the kind. Valid sides are: +# - "left" (if not specified, uses this) +# - "right" +# - "top" +# - "bottom" +# For instance, "widget,right" is valid; so is "qml", which defaults +# to putting the sidebar on the left. Also valid is "qml,top". +# While "widget,top" is valid, the widgets code is **not** flexible +# and results will be terrible. sidebar: widget # Kind of navigation (button panel on the bottom). # - "widget" or unset, use traditional navigation # - "none", hide it entirely # - "qml", use calamares-navigation.qml from branding folder +# In addition, you **may** specify a side, separated by a comma, +# from the kind. The same sides are valid as for *sidebar*, +# except the default is *bottom*. navigation: widget # These are strings shown to the user in the user interface. diff --git a/src/calamares/CalamaresWindow.cpp b/src/calamares/CalamaresWindow.cpp index 5d4565406..0e8ac1421 100644 --- a/src/calamares/CalamaresWindow.cpp +++ b/src/calamares/CalamaresWindow.cpp @@ -132,11 +132,10 @@ CalamaresWindow::getWidgetSidebar( int desiredWidth ) } QWidget* -CalamaresWindow::getQmlSidebar( int desiredWidth ) +CalamaresWindow::getQmlSidebar( int ) { CalamaresUtils::registerCalamaresModels(); QQuickWidget* w = new QQuickWidget( this ); - w->setFixedWidth( desiredWidth ); w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); w->setResizeMode( QQuickWidget::SizeRootObjectToView ); w->setSource( QUrl( @@ -217,7 +216,6 @@ CalamaresWindow::getQmlNavigation() { CalamaresUtils::registerCalamaresModels(); QQuickWidget* w = new QQuickWidget( this ); - w->setFixedHeight( 64 ); w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); w->setResizeMode( QQuickWidget::SizeRootObjectToView ); w->setSource( QUrl( @@ -251,6 +249,28 @@ flavoredWidget( Calamares::Branding::PanelFlavor flavor, NOTREACHED return nullptr; // All enum values handled above } +/** @brief Adds widgets to @p layout if they belong on this @p side + */ +static inline void +insertIf( QBoxLayout* layout, + Calamares::Branding::PanelSide side, + QWidget* first, + Calamares::Branding::PanelSide firstSide ) +{ + if ( first && side == firstSide ) + { + if ( ( side == Calamares::Branding::PanelSide::Left ) || ( side == Calamares::Branding::PanelSide::Right ) ) + { + first->setMinimumWidth( qMax( first->minimumWidth(), 64 ) ); + } + else + { + first->setMinimumHeight( qMax( first->minimumHeight(), 64 ) ); + } + layout->addWidget( first ); + } +} + CalamaresWindow::CalamaresWindow( QWidget* parent ) : QWidget( parent ) , m_debugWindow( nullptr ) @@ -273,6 +293,8 @@ CalamaresWindow::CalamaresWindow( QWidget* parent ) using CalamaresUtils::windowPreferredHeight; using CalamaresUtils::windowPreferredWidth; + using PanelSide = Calamares::Branding::PanelSide; + // Needs to match what's checked in DebugWindow this->setObjectName( "mainApp" ); @@ -292,21 +314,6 @@ CalamaresWindow::CalamaresWindow( QWidget* parent ) resize( w, h ); m_viewManager = Calamares::ViewManager::instance( this ); - - QBoxLayout* mainLayout = new QHBoxLayout; - setLayout( mainLayout ); - - QWidget* sideBox = flavoredWidget( - branding->sidebarFlavor(), - this, - &CalamaresWindow::getWidgetSidebar, - &CalamaresWindow::getQmlSidebar, - qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) ); - if ( sideBox ) - { - mainLayout->addWidget( sideBox ); - } - if ( branding->windowExpands() ) { connect( m_viewManager, &Calamares::ViewManager::enlarge, this, &CalamaresWindow::enlarge ); @@ -319,16 +326,35 @@ CalamaresWindow::CalamaresWindow( QWidget* parent ) // and requires an extra show() (at least with KWin/X11) which // is too annoying. Instead, leave it up to ignoring-the-quit- // event, which is also the ViewManager's responsibility. + + QBoxLayout* mainLayout = new QHBoxLayout; QBoxLayout* contentsLayout = new QVBoxLayout; - contentsLayout->addWidget( m_viewManager->centralWidget() ); + + setLayout( mainLayout ); + + QWidget* sideBox = flavoredWidget( + branding->sidebarFlavor(), + this, + &CalamaresWindow::getWidgetSidebar, + &CalamaresWindow::getQmlSidebar, + qBound( 100, CalamaresUtils::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) ); QWidget* navigation = flavoredWidget( branding->navigationFlavor(), this, &CalamaresWindow::getWidgetNavigation, &CalamaresWindow::getQmlNavigation ); - if ( navigation ) - { - contentsLayout->addWidget( navigation ); - } + // Build up the contentsLayout (a VBox) top-to-bottom + // .. note that the bottom is mirrored wrt. the top + insertIf( contentsLayout, PanelSide::Top, sideBox, branding->sidebarSide() ); + insertIf( contentsLayout, PanelSide::Top, navigation, branding->navigationSide() ); + contentsLayout->addWidget( m_viewManager->centralWidget() ); + insertIf( contentsLayout, PanelSide::Bottom, navigation, branding->navigationSide() ); + insertIf( contentsLayout, PanelSide::Bottom, sideBox, branding->sidebarSide() ); + + // .. and then the mainLayout left-to-right + insertIf( mainLayout, PanelSide::Left, sideBox, branding->sidebarSide() ); + insertIf( mainLayout, PanelSide::Left, navigation, branding->navigationSide() ); mainLayout->addLayout( contentsLayout ); + insertIf( mainLayout, PanelSide::Right, navigation, branding->navigationSide() ); + insertIf( mainLayout, PanelSide::Right, sideBox, branding->sidebarSide() ); CalamaresUtils::unmarginLayout( mainLayout ); CalamaresUtils::unmarginLayout( contentsLayout ); diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index 25fab307e..ff7e43fb8 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -405,6 +405,79 @@ getString( const YAML::Node& doc, const char* key ) return QString(); } +static inline void +flavorAndSide( const YAML::Node& doc, const char* key, Branding::PanelFlavor& flavor, Branding::PanelSide& side ) +{ + using PanelFlavor = Branding::PanelFlavor; + using PanelSide = Branding::PanelSide; + + // *INDENT-OFF* + // clang-format off + static const NamedEnumTable< PanelFlavor > sidebarFlavorNames { + { QStringLiteral( "widget" ), PanelFlavor::Widget }, + { QStringLiteral( "none" ), PanelFlavor::None }, + { QStringLiteral( "hidden" ), PanelFlavor::None }, + { QStringLiteral( "qml" ), PanelFlavor::Qml } + }; + static const NamedEnumTable< PanelSide > panelSideNames { + { QStringLiteral( "left" ), PanelSide::Left }, + { QStringLiteral( "right" ), PanelSide::Right }, + { QStringLiteral( "top" ), PanelSide::Top }, + { QStringLiteral( "bottom" ), PanelSide::Bottom } + }; + // clang-format on + // *INDENT-ON* + + bool ok = false; + QString configValue = getString( doc, key ); + if ( configValue.isEmpty() ) + { + // Complain with the original values + cWarning() << "Branding setting for" << key << "is missing, using" << sidebarFlavorNames.find( flavor, ok ) + << panelSideNames.find( side, ok ); + return; + } + + QStringList parts = configValue.split( ',' ); + if ( parts.length() == 1 ) + { + PanelFlavor f = sidebarFlavorNames.find( configValue, ok ); + if ( ok ) + { + flavor = f; + } + else + { + // Complain with the original value + cWarning() << "Branding setting for" << key << "interpreted as" << sidebarFlavorNames.find( flavor, ok ) + << panelSideNames.find( side, ok ); + } + return; + } + + for ( const QString& spart : parts ) + { + bool isFlavor = false; + bool isSide = false; + PanelFlavor f = sidebarFlavorNames.find( spart, isFlavor ); + PanelSide s = panelSideNames.find( spart, isSide ); + if ( isFlavor ) + { + flavor = f; + } + else if ( isSide ) + { + side = s; + } + else + { + cWarning() << "Branding setting for" << key << "contains unknown" << spart << "interpreted as" + << sidebarFlavorNames.find( flavor, ok ) << panelSideNames.find( side, ok ); + return; + } + } +} + void Branding::initSimpleSettings( const YAML::Node& doc ) { @@ -419,12 +492,6 @@ Branding::initSimpleSettings( const YAML::Node& doc ) { QStringLiteral( "free" ), WindowPlacement::Free }, { QStringLiteral( "center" ), WindowPlacement::Center } }; - static const NamedEnumTable< PanelFlavor > sidebarFlavorNames { - { QStringLiteral( "widget" ), PanelFlavor::Widget }, - { QStringLiteral( "none" ), PanelFlavor::None }, - { QStringLiteral( "hidden" ), PanelFlavor::None }, - { QStringLiteral( "qml" ), PanelFlavor::Qml } - }; // clang-format on // *INDENT-ON* bool ok = false; @@ -443,18 +510,8 @@ Branding::initSimpleSettings( const YAML::Node& doc ) cWarning() << "Branding module-setting *windowPlacement* interpreted as" << placementNames.find( m_windowPlacement, ok ); } - m_sidebarFlavor = sidebarFlavorNames.find( getString( doc, "sidebar" ), ok ); - if ( !ok ) - { - cWarning() << "Branding module-setting *sidebar* interpreted as" - << sidebarFlavorNames.find( m_sidebarFlavor, ok ); - } - m_navigationFlavor = sidebarFlavorNames.find( getString( doc, "navigation" ), ok); - if ( !ok ) - { - cWarning() << "Branding module-setting *navigation* interpreted as" - << sidebarFlavorNames.find( m_navigationFlavor, ok ); - } + flavorAndSide( doc, "sidebar", m_sidebarFlavor, m_sidebarSide ); + flavorAndSide( doc, "navigation", m_navigationFlavor, m_navigationSide ); QString windowSize = getString( doc, "windowSize" ); if ( !windowSize.isEmpty() ) diff --git a/src/libcalamaresui/Branding.h b/src/libcalamaresui/Branding.h index b7ba637d6..023f1a511 100644 --- a/src/libcalamaresui/Branding.h +++ b/src/libcalamaresui/Branding.h @@ -123,7 +123,7 @@ public: Free }; Q_ENUM( WindowPlacement ) - ///@brief What kind of sidebar to use in the main window + ///@brief What kind of panel (sidebar, navigation) to use in the main window enum class PanelFlavor { None, @@ -131,6 +131,16 @@ public: Qml }; Q_ENUM( PanelFlavor ) + ///@brief Where to place a panel (sidebar, navigation) + enum class PanelSide + { + None, + Left, + Right, + Top, + Bottom + }; + Q_ENUM( PanelSide ) static Branding* instance(); @@ -201,6 +211,9 @@ public slots: QString styleString( StyleEntry styleEntry ) const; QString imagePath( ImageEntry imageEntry ) const; + PanelSide sidebarSide() const { return m_sidebarSide; } + PanelSide navigationSide() const { return m_navigationSide; } + private: static Branding* s_instance; @@ -231,6 +244,8 @@ private: PanelFlavor m_sidebarFlavor = PanelFlavor::Widget; PanelFlavor m_navigationFlavor = PanelFlavor::Widget; + PanelSide m_sidebarSide = PanelSide::Left; + PanelSide m_navigationSide = PanelSide::Bottom; }; template < typename U >