qskinny/designsystems/fluent2/QskFluent2Skin.cpp

2168 lines
71 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
/*
Definitions ( where possible ) taken from
https://www.figma.com/file/NAWMapFlXnoOb86Q2H5GKr/Windows-UI-(Community)
*/
/*
TODO:
- we have a lot of lines with 1 pixels. Unfortunately OpenGL does some sort
of antialiasing, when a line is not a pixel position. On screens with high
resolution usually no probem, but f.e on mine ...
- QskCheckBox::Error is not properly supported
- Pressed state is missing
- QskComboBox
- QskPageIndicator
- Hovered state is missing
- QskPageIndicator
- QskListView
- Indicator subcontrol might be better than using the border of the selection box
- cell padding unclear
*/
/*
The palette is made of a couple of configurable base/accent colors and a
predefined set of semitransparent shades of gray. These grays are used
to create darkened/lightend colors from the configurable colors that can
be used for borders etc.
However borders need to be darkened for light base colors and lightened for
dark ones. So we actually have 2 different sets of grays for light/dark
colors schemes.
The main advantage of this concept is, that a working color schemes
can be defined by setting the accent/base colors only.
*/
#include "QskFluent2Skin.h"
#include "QskFluent2Theme.h"
#include "QskFluent2TextFieldSkinlet.h"
#include <QskSkinHintTableEditor.h>
#include <QskBox.h>
#include <QskCheckBox.h>
#include <QskComboBox.h>
#include <QskDialogButtonBox.h>
#include <QskDialogSubWindow.h>
#include <QskDrawer.h>
#include <QskFocusIndicator.h>
#include <QskGraphicLabel.h>
#include <QskListView.h>
#include <QskMenu.h>
#include <QskPageIndicator.h>
#include <QskPushButton.h>
#include <QskProgressBar.h>
#include <QskProgressRing.h>
#include <QskRadioBox.h>
#include <QskScrollView.h>
#include <QskSegmentedBar.h>
#include <QskSeparator.h>
#include <QskShadowMetrics.h>
#include <QskSlider.h>
#include <QskSpinBox.h>
#include <QskStandardSymbol.h>
#include <QskSubWindow.h>
#include <QskSwitchButton.h>
#include <QskSwitchButtonSkinlet.h>
#include <QskTabBar.h>
#include <QskTabButton.h>
#include <QskTabView.h>
#include <QskTextField.h>
#include <QskTextLabel.h>
#include <QskVirtualKeyboard.h>
#include <QskAnimationHint.h>
#include <QskAspect.h>
#include <QskBoxBorderColors.h>
#include <QskBoxBorderMetrics.h>
#include <QskBoxShapeMetrics.h>
#include <QskColorFilter.h>
#include <QskFunctions.h>
#include <QskGraphic.h>
#include <QskGraphicIO.h>
#include <QskMargins.h>
#include <QskRgbValue.h>
#include <QskFontRole.h>
#include <QskNamespace.h>
#include <QskPlatform.h>
#include <qguiapplication.h>
#include <qfontinfo.h>
static void qskFluent2InitResources()
{
Q_INIT_RESOURCE( QskFluent2Icons );
}
Q_CONSTRUCTOR_FUNCTION( qskFluent2InitResources )
namespace Fluent2
{
using F = QskFontRole;
/*
Fluent2/Windows font roles according to:
https://fluent2.microsoft.design/typography
*/
constexpr F Caption = { F::Caption, F::Normal };
constexpr F Body = { F::Body, F::Normal };
constexpr F BodyStrong = { F::Body, F::High };
constexpr F BodyStronger = { F::Body, F::VeryHigh };
constexpr F Subtitle = { F::Subtitle, F::Normal };
constexpr F Title = { F::Title, F::Normal };
constexpr F LargeTitle = { F::Headline, F::Normal };
constexpr F Display = { F::Display, F::Normal };
}
namespace
{
/*
mapping between px and logical coordinates
*/
inline constexpr double operator ""_px( long double value )
{
return static_cast< double >( value );
}
inline constexpr double operator ""_px( unsigned long long value )
{
return static_cast< double >( value );
}
inline constexpr QRgb rgbGray( int value, qreal opacity = 1.0 )
{
return qRgba( value, value, value, qRound( opacity * 255 ) );
}
/*
When application code is manipulating base colors manually the colors of
the borders will be lightened/darkened from it like expected - as long
as the application color matches the color scheme. Otherwise we end
up with lightened borders on light backgrounds or v.v.
To avoid this problem we could resolve the grays with the background
colors of the sections at definition time. This solves the problem with
applications using backgrounds from the "wrong" color scheme, but requires
more work for customizing controls hen using the "correct" scheme
( -> border colors need to be set as well ).
When enabling QSK_RESOLVE_COLORS a code path is enabled that is intended
to play with resolving the grays at definition time. But I'm not decided
if it is worth to make a feature from it. TODO ...
*/
#define QSK_RESOLVE_COLORS 0
#if QSK_RESOLVE_COLORS
inline constexpr QRgb rgbFlattened( QRgb foreground, QRgb background )
{
Q_ASSERT( qAlpha( background ) == 255 );
const auto r2 = qAlpha( foreground ) / 255.0;
const auto r1 = 1.0 - r2;
const auto r = qRound( r1 * qRed( background ) + r2 * qRed( foreground ) );
const auto g = qRound( r1 * qGreen( background ) + r2 * qGreen( foreground ) );
const auto b = qRound( r1 * qBlue( background ) + r2 * qBlue( foreground ) );
return qRgb( r, g, b );
}
inline constexpr QRgb rgbSolid( QRgb foreground, QRgb background )
{
/*
dummy method, so that we can compare the results with
or without resolving the foreground alpha value
*/
return rgbFlattened( foreground, background );
}
#else // !QSK_RESOLVE_COLORS
inline QRgb rgbFlattened( QRgb foreground, QRgb background )
{
const auto alpha = qAlpha( foreground ) / 255.0;
return QskRgb::interpolated( background, foreground, alpha );
}
inline constexpr QRgb rgbSolid( QRgb foreground, QRgb background )
{
Q_UNUSED( background );
return foreground;
}
#endif
class Editor : private QskSkinHintTableEditor
{
public:
Editor( QskSkinHintTable* table )
: QskSkinHintTableEditor( table )
{
}
void setupMetrics();
void setupColors( QskAspect::Section, const QskFluent2Theme& );
private:
void setupPopup( const QskFluent2Theme& );
void setupSubWindow( const QskFluent2Theme& );
void setupDialogSubWindow( const QskFluent2Theme& );
void setupBoxMetrics();
void setupBoxColors( QskAspect::Section, const QskFluent2Theme& );
void setupCheckBoxMetrics();
void setupCheckBoxColors( QskAspect::Section, const QskFluent2Theme& );
void setupComboBoxMetrics();
void setupComboBoxColors( QskAspect::Section, const QskFluent2Theme& );
void setupDialogButtonBoxMetrics();
void setupDialogButtonBoxColors( QskAspect::Section, const QskFluent2Theme& );
void setupDrawerMetrics();
void setupDrawerColors( QskAspect::Section, const QskFluent2Theme& );
void setupFocusIndicatorMetrics();
void setupFocusIndicatorColors( QskAspect::Section, const QskFluent2Theme& );
void setupGraphicLabelMetrics();
void setupGraphicLabelColors( QskAspect::Section, const QskFluent2Theme& );
void setupListViewMetrics();
void setupListViewColors( QskAspect::Section, const QskFluent2Theme& );
void setupMenuMetrics();
void setupMenuColors( QskAspect::Section, const QskFluent2Theme& );
void setupPageIndicatorMetrics();
void setupPageIndicatorColors( QskAspect::Section, const QskFluent2Theme& );
void setupProgressBarMetrics();
void setupProgressBarColors( QskAspect::Section, const QskFluent2Theme& );
void setupProgressRingMetrics();
void setupProgressRingColors( QskAspect::Section, const QskFluent2Theme& );
void setupPushButtonMetrics();
void setupPushButtonColors( QskAspect::Section, const QskFluent2Theme& );
void setupRadioBoxMetrics();
void setupRadioBoxColors( QskAspect::Section, const QskFluent2Theme& );
void setupScrollViewMetrics();
void setupScrollViewColors( QskAspect::Section, const QskFluent2Theme& );
void setupSegmentedBarMetrics();
void setupSegmentedBarColors( QskAspect::Section, const QskFluent2Theme& );
void setupSeparatorMetrics();
void setupSeparatorColors( QskAspect::Section, const QskFluent2Theme& );
void setupSliderMetrics();
void setupSliderColors( QskAspect::Section, const QskFluent2Theme& );
void setupSpinBoxMetrics();
void setupSpinBoxColors( QskAspect::Section, const QskFluent2Theme& );
void setupSwitchButtonMetrics();
void setupSwitchButtonColors( QskAspect::Section, const QskFluent2Theme& );
void setupTabButtonMetrics();
void setupTabButtonColors( QskAspect::Section, const QskFluent2Theme& );
void setupTabBarMetrics();
void setupTabBarColors( QskAspect::Section, const QskFluent2Theme& );
void setupTabViewMetrics();
void setupTabViewColors( QskAspect::Section, const QskFluent2Theme& );
void setupTextFieldMetrics();
void setupTextFieldColors( QskAspect::Section, const QskFluent2Theme& );
void setupTextLabelMetrics();
void setupTextLabelColors( QskAspect::Section, const QskFluent2Theme& );
void setupVirtualKeyboardMetrics();
void setupVirtualKeyboardColors( QskAspect::Section, const QskFluent2Theme& );
inline QskGraphic symbol( const char* name ) const
{
const QString path = QStringLiteral( ":fluent2/icons/qvg/" )
+ name + QStringLiteral( ".qvg" );
return QskGraphicIO::read( path );
}
inline void setBoxBorderGradient( QskAspect aspect,
QRgb border1, QRgb border2, QRgb baseColor )
{
border1 = rgbFlattened( border1, baseColor );
border2 = rgbFlattened( border2, baseColor );
setBoxBorderColors( aspect, { border1, border1, border1, border2 } );
}
inline void setBoxBorderGradient( QskAspect aspect,
const QskFluent2Theme::BorderGradient& gradient, QRgb baseColor )
{
setBoxBorderGradient( aspect, gradient[ 0 ], gradient[ 1 ], baseColor );
}
};
}
void Editor::setupMetrics()
{
setupBoxMetrics();
setupCheckBoxMetrics();
setupComboBoxMetrics();
setupDialogButtonBoxMetrics();
setupDrawerMetrics();
setupFocusIndicatorMetrics();
setupGraphicLabelMetrics();
setupListViewMetrics();
setupMenuMetrics();
setupPageIndicatorMetrics();
setupProgressBarMetrics();
setupProgressRingMetrics();
setupPushButtonMetrics();
setupRadioBoxMetrics();
setupScrollViewMetrics();
setupSegmentedBarMetrics();
setupSeparatorMetrics();
setupSliderMetrics();
setupSpinBoxMetrics();
setupSwitchButtonMetrics();
setupTabButtonMetrics();
setupTabBarMetrics();
setupTabViewMetrics();
setupTextFieldMetrics();
setupTextLabelMetrics();
setupVirtualKeyboardMetrics();
}
void Editor::setupColors( QskAspect::Section section, const QskFluent2Theme& theme )
{
if ( section == QskAspect::Body )
{
// TODO
setupPopup( theme );
setupSubWindow( theme );
setupDialogSubWindow( theme );
}
setupBoxColors( section, theme );
setupCheckBoxColors( section, theme );
setupComboBoxColors( section, theme );
setupDialogButtonBoxColors( section, theme );
setupDrawerColors( section, theme );
setupFocusIndicatorColors( section, theme );
setupGraphicLabelColors( section, theme );
setupGraphicLabelMetrics();
setupListViewColors( section, theme );
setupMenuColors( section, theme );
setupPageIndicatorColors( section, theme );
setupProgressBarColors( section, theme );
setupProgressRingColors( section, theme );
setupPushButtonColors( section, theme );
setupRadioBoxColors( section, theme );
setupScrollViewColors( section, theme );
setupSegmentedBarColors( section, theme );
setupSeparatorColors( section, theme );
setupSliderColors( section, theme );
setupSwitchButtonColors( section, theme );
setupSpinBoxColors( section, theme );
setupTabButtonColors( section, theme );
setupTabBarColors( section, theme );
setupTabViewColors( section, theme );
setupTextFieldColors( section, theme );
setupTextLabelColors( section, theme );
setupVirtualKeyboardColors( section, theme );
};
void Editor::setupBoxMetrics()
{
}
void Editor::setupBoxColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
setGradient( QskBox::Panel | section,
theme.palette.background.solid.base );
}
void Editor::setupCheckBoxMetrics()
{
using Q = QskCheckBox;
setStrutSize( Q::Panel, 126_px, 38_px );
setSpacing( Q::Panel, 8_px );
setStrutSize( Q::Box, { 20_px, 20_px } ); // 18 + 2*1 border
setBoxShape( Q::Box, 4_px ); // adapt to us taking the border into account
setBoxBorderMetrics( Q::Box, 1_px );
setFontRole( Q::Text, Fluent2::Body );
}
void Editor::setupCheckBoxColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskCheckBox;
using A = QskAspect;
const auto& pal = theme.palette;
const auto checkMark = symbol( "checkmark" );
for ( const auto state1 : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
{
QRgb fillColor, borderColor, textColor;
for ( const auto state2 : { A::NoState, Q::Checked } )
{
const auto states = state1 | state2;
if ( states == A::NoState )
{
fillColor = pal.fillColor.controlAlt.secondary;
borderColor = pal.strokeColor.controlStrong.defaultColor;
textColor = pal.fillColor.text.primary;
}
else if ( states == Q::Hovered )
{
fillColor = pal.fillColor.controlAlt.tertiary;
borderColor = pal.strokeColor.controlStrong.defaultColor;
textColor = pal.fillColor.text.primary;
}
else if ( states == ( Q::Hovered | Q::Checked ) )
{
fillColor = pal.fillColor.accent.secondary;
borderColor = fillColor;
textColor = pal.fillColor.text.primary;
}
else if ( states == Q::Checked )
{
fillColor = pal.fillColor.accent.defaultColor;
borderColor = pal.fillColor.accent.defaultColor;
textColor = pal.fillColor.text.primary;
}
else if ( states == Q::Pressed )
{
fillColor = pal.fillColor.controlAlt.quaternary;
borderColor = pal.strokeColor.controlStrong.disabled;
textColor = pal.fillColor.text.primary;
}
else if ( states == ( Q::Pressed | Q::Checked ) )
{
fillColor = pal.fillColor.accent.tertiary;
borderColor = pal.fillColor.accent.tertiary;
textColor = pal.fillColor.text.primary;
setSymbol( Q::Indicator | states, checkMark );
}
else if ( states == Q::Disabled )
{
fillColor = pal.fillColor.controlAlt.disabled;
borderColor = pal.strokeColor.controlStrong.disabled;
textColor = pal.fillColor.text.disabled;
}
else if ( states == ( Q::Disabled | Q::Checked ) )
{
fillColor = pal.fillColor.accent.disabled;
borderColor = pal.fillColor.accent.disabled;
textColor = pal.fillColor.text.disabled;
}
/*
Support for QskCheckBox::Error is not properly defined.
Doing some basic definitions, so that we can at least
see the boxes with this state. TODO ...
*/
for ( const auto state3 : { A::NoState, Q::Error } )
{
const auto box = Q::Box | section | states | state3;
const auto text = Q::Text | section | states | state3;
const auto indicator = Q::Indicator | section | states | state3;
#if 1
if ( state3 == Q::Error && !( states & Q::Disabled ) )
{
borderColor = QskRgb::IndianRed;
if ( states & Q::Checked )
fillColor = QskRgb::DarkRed;
}
#endif
fillColor = rgbSolid( fillColor, pal.background.solid.base );
setGradient( box, fillColor );
borderColor = rgbSolid( borderColor, fillColor );
setBoxBorderColors( box, borderColor );
setColor( text, textColor );
if ( states & Q::Checked )
{
setGraphicRole( indicator, ( states & Q::Disabled )
? QskFluent2Skin::GraphicRoleFillColorTextOnAccentDisabled
: QskFluent2Skin::GraphicRoleFillColorTextOnAccentPrimary );
setSymbol( indicator, checkMark );
}
}
}
}
}
void Editor::setupComboBoxMetrics()
{
using Q = QskComboBox;
setStrutSize( Q::Panel, { -1, 32_px } );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxShape( Q::Panel, 3_px );
setPadding( Q::Panel, { 11_px, 0_px, 11_px, 0_px } );
setStrutSize( Q::Icon, 12_px, 12_px );
setPadding( Q::Icon, { 0, 0, 8_px, 0 } );
setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Text, Fluent2::Body );
setStrutSize( Q::StatusIndicator, 16_px, 16_px );
setSymbol( Q::StatusIndicator, symbol( "chevron_down" ) );
// setSymbol( Q::StatusIndicator | Q::PopupOpen, symbol( "chevron_up" ) );
// Using Focused (Pressed doesn't exist yet):
setBoxBorderMetrics( Q::Panel | Q::Focused, { 1_px, 1_px, 1_px, 2_px } );
}
void Editor::setupComboBoxColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskComboBox;
using W = QskFluent2Skin;
const auto& pal = theme.palette;
for ( const auto state :
{ QskAspect::NoState, Q::Hovered, Q::Focused, Q::Pressed, Q::Disabled } )
{
QRgb panelColor, borderColor1, borderColor2, textColor;
if ( state == QskAspect::NoState )
{
panelColor = pal.fillColor.control.defaultColor;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[1];
textColor = pal.fillColor.text.primary;
}
else if ( state == Q::Hovered )
{
panelColor = pal.fillColor.control.secondary;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.elevation.textControl.border[1];
textColor = pal.fillColor.text.primary;
}
else if ( state == Q::Focused )
{
panelColor = pal.fillColor.control.inputActive;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.fillColor.accent.defaultColor;
textColor = pal.fillColor.text.primary;
}
else if ( state == Q::Pressed )
{
panelColor = pal.fillColor.control.inputActive;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.fillColor.accent.defaultColor;
textColor = pal.fillColor.text.secondary;
}
else if ( state == Q::Disabled )
{
panelColor = pal.fillColor.control.disabled;
borderColor2 = borderColor1 = pal.strokeColor.control.defaultColor;
textColor = pal.fillColor.text.disabled;
}
const auto panel = Q::Panel | section | state;
const auto text = Q::Text | section | state;
const auto icon = Q::Icon | section | state;
const auto indicator = Q::StatusIndicator | section | state;
panelColor = rgbSolid( panelColor, pal.background.solid.base );
setGradient( panel, panelColor );
setBoxBorderGradient( panel, borderColor1, borderColor2, panelColor );
setColor( text, textColor );
if ( state == Q::Disabled )
{
setGraphicRole( icon, W::GraphicRoleFillColorTextDisabled );
setGraphicRole( indicator, W::GraphicRoleFillColorTextDisabled );
}
else if( state == Q::Pressed )
{
setGraphicRole( icon, W::GraphicRoleFillColorTextSecondary );
setGraphicRole( indicator, W::GraphicRoleFillColorTextSecondary );
}
else
{
setGraphicRole( icon, W::GraphicRoleFillColorTextPrimary );
setGraphicRole( indicator, W::GraphicRoleFillColorTextSecondary );
}
}
}
void Editor::setupDialogButtonBoxMetrics()
{
setPadding( QskDialogButtonBox::Panel, 20_px );
}
void Editor::setupDialogButtonBoxColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
setGradient( QskDialogButtonBox::Panel | section,
theme.palette.background.solid.base );
}
void Editor::setupDrawerMetrics()
{
using Q = QskDrawer;
using A = QskAspect;
setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic );
}
void Editor::setupDrawerColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
setGradient( QskDrawer::Panel | section,
theme.palette.background.solid.base );
}
void Editor::setupFocusIndicatorMetrics()
{
using Q = QskFocusIndicator;
setBoxBorderMetrics( Q::Panel, 2_px );
setPadding( Q::Panel, 3_px );
setBoxShape( Q::Panel, 4_px );
}
void Editor::setupFocusIndicatorColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskFocusIndicator;
using A = QskAspect;
const auto& pal = theme.palette;
const auto aspect = Q::Panel | section;
const auto color = pal.strokeColor.focus.outer;
setBoxBorderColors( aspect, color );
setBoxBorderColors( aspect | Q::Disabled, QskRgb::toTransparent( color, 0 ) );
setAnimation( Q::Panel | A::Color, 200 );
setAnimation( Q::Panel | A::Color | Q::Disabled, 500 );
}
void Editor::setupListViewMetrics()
{
using Q = QskListView;
using A = QskAspect;
for ( auto state : { A::NoState, Q::Hovered, Q::Pressed } )
setBoxBorderMetrics( Q::Cell | state | Q::Selected, { 3_px, 0, 0, 0 } );
#if 1
// taken from M3 - what are the actual values, TODO ...
setPadding( Q::Cell, { 16_px, 12_px, 16_px, 12_px } );
#endif
}
void Editor::setupListViewColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskListView;
using A = QskAspect;
const auto& pal = theme.palette;
for ( const auto state1 : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
{
QRgb textColor, indicatorColor;
if ( state1 == Q::Disabled )
{
textColor = pal.fillColor.text.disabled;
indicatorColor = pal.fillColor.accent.disabled;
}
else if ( state1 == Q::Pressed )
{
textColor = pal.fillColor.text.secondary;
indicatorColor = pal.fillColor.accent.defaultColor;
}
else
{
textColor = pal.fillColor.text.primary;
indicatorColor = pal.fillColor.accent.defaultColor;
}
for ( const auto state2 : { A::NoState, Q::Selected } )
{
QRgb cellColor;
if ( state2 == A::NoState )
{
if ( state1 == Q::Hovered )
cellColor = pal.fillColor.subtle.secondary;
else if ( state1 == Q::Pressed )
cellColor = pal.fillColor.subtle.tertiary;
else
cellColor = Qt::transparent;
}
else
{
if ( state1 == Q::Hovered )
cellColor = pal.fillColor.subtle.tertiary;
else
cellColor = pal.fillColor.subtle.secondary;
}
const auto cell = Q::Cell | section | state1 | state2;
const auto text = Q::Text | section | state1 | state2;
setGradient( cell, cellColor );
{
/*
We are using a section of the left border to display a
bar indicating the selection. Maybe we should introduce a
subcontrol instead TODO ...
*/
const auto c1 = cellColor;
const auto c2 = indicatorColor;
const auto p1 = ( state1 == Q::Pressed ) ? 0.33 : 0.25;
const auto p2 = 1.0 - p1;
setBoxBorderColors( cell,
QskGradient( { { p1, c1 }, { p1, c2 }, { p2, c2 }, { p2, c1 } } ) );
}
setColor( text, textColor );
}
}
setAnimation( Q::Cell | A::Color, 100 );
setAnimation( Q::Text | A::Color, 100 );
}
void Editor::setupMenuMetrics()
{
using Q = QskMenu;
using A = QskAspect;
setPadding( Q::Panel, { 4_px, 6_px, 4_px, 6_px } );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxShape( Q::Panel, 7_px );
setPadding( Q::Segment, { 0, 10_px, 10_px, 10_px } );
setSpacing( Q::Segment, 15_px );
setBoxBorderMetrics( Q::Segment | Q::Selected, { 3_px, 0, 0, 0 } );
setFontRole( Q::Text, Fluent2::Body );
setStrutSize( Q::Icon, 12_px, 12_px );
setPadding( Q::Icon, { 8_px, 8_px, 0, 8_px } );
setAnimation( Q::Panel | A::Position, 100 );
}
void Editor::setupMenuColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
Q_UNUSED( section );
using Q = QskMenu;
#if 1
setShadowMetrics( Q::Panel, theme.shadow.flyout.metrics );
#endif
const auto& pal = theme.palette;
setBoxBorderColors( Q::Panel, pal.strokeColor.surface.flyout );
setGradient( Q::Panel, pal.background.flyout.defaultColor );
setShadowColor( Q::Panel, theme.shadow.flyout.color );
setGradient( Q::Segment | Q::Hovered, pal.fillColor.subtle.secondary );
setGradient( Q::Segment | Q::Selected | Q::Pressed, pal.fillColor.subtle.tertiary );
setGradient( Q::Segment | Q::Selected, pal.fillColor.subtle.secondary );
/*
We are using a section of the left border to display a
bar indicating the selection. Maybe we should introduce a
subcontrol instead TODO ...
*/
const auto c1 = pal.fillColor.subtle.secondary;
const auto c2 = pal.fillColor.accent.defaultColor;
setBoxBorderColors( Q::Segment | Q::Selected,
QskGradient( { { 0.25, c1 }, { 0.25, c2 }, { 0.75, c2 }, { 0.75, c1 } } ) );
setBoxBorderColors( Q::Segment | Q::Selected | Q::Pressed,
QskGradient( { { 0.33, c1 }, { 0.33, c2 }, { 0.67, c2 }, { 0.67, c1 } } ) );
setColor( Q::Text, pal.fillColor.text.primary );
setColor( Q::Text | Q::Selected | Q::Pressed, pal.fillColor.text.secondary );
setGraphicRole( Q::Icon, QskFluent2Skin::GraphicRoleFillColorTextPrimary );
setGraphicRole( Q::Icon | Q::Selected | Q::Pressed,
QskFluent2Skin::GraphicRoleFillColorTextSecondary );
}
void Editor::setupPageIndicatorMetrics()
{
using Q = QskPageIndicator;
using A = QskAspect;
setPadding( Q::Panel, 5_px );
setSpacing( Q::Panel, 6_px );
// circles, without border
setBoxShape( Q::Bullet, 100, Qt::RelativeSize );
setBoxBorderMetrics( Q::Bullet, 0 );
/*
Pressed/Hovered are not yet implemented.
Sizes would be:
- Q::Pressed : 3
- Q::Pressed | Q::Selected : 5
- Q::Hovered : 5
- Q::Hovered | Q::Selected : 7
*/
setStrutSize( Q::Bullet, 6_px, 6_px );
/*
Using the margin to adjust sizes is a weired type of implementation.
Needs to be changed TODO ...
*/
for ( auto state : { A::NoState, Q::Disabled } )
{
setMargin( Q::Bullet | state | Q::Selected, 0 );
setMargin( Q::Bullet | state, 1_px );
}
}
void Editor::setupPageIndicatorColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskPageIndicator;
using A = QskAspect;
const auto& pal = theme.palette;
setGradient( Q::Panel, QskGradient() );
// Pressed/Hovered are missing - color for both would be pal.fillColor.text.secondary
for ( const auto state : { A::NoState, Q::Disabled } )
{
const auto color = ( state == A::NoState )
? pal.fillColor.controlStrong.defaultColor
: pal.fillColor.controlStrong.disabled;
const auto bullet = Q::Bullet | state | section;
setGradient( bullet, color );
setGradient( bullet | Q::Selected, color );
}
}
void Editor::setupPopup( const QskFluent2Theme& theme )
{
using Q = QskPopup;
using A = QskAspect;
const auto& pal = theme.palette;
setHint( Q::Overlay | A::Style, true );
setGradient( Q::Overlay, pal.background.overlay.defaultColor );
}
void Editor::setupProgressBarMetrics()
{
using Q = QskProgressBar;
using A = QskAspect;
setMetric( Q::Groove | A::Size, 1_px );
setBoxShape( Q::Groove, 100, Qt::RelativeSize );
setMetric( Q::Fill | A::Size, 3_px );
setBoxShape( Q::Fill, 100, Qt::RelativeSize );
}
void Editor::setupProgressBarColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskProgressBar;
const auto& pal = theme.palette;
setGradient( Q::Groove | section, pal.strokeColor.controlStrong.defaultColor );
setGradient( Q::Fill | section, pal.fillColor.accent.defaultColor );
}
void Editor::setupProgressRingMetrics()
{
using Q = QskProgressRing;
using A = QskAspect;
static constexpr QskAspect::Variation SmallSize = A::Small;
static constexpr QskAspect::Variation NormalSize = A::NoVariation;
static constexpr QskAspect::Variation LargeSize = A::Large;
setStrutSize( Q::Fill | SmallSize, { 16_px, 16_px } );
setStrutSize( Q::Fill | NormalSize, { 32_px, 32_px } );
setStrutSize( Q::Fill | LargeSize, { 64_px, 64_px } );
const auto startAngle = 90, spanAngle = -360;
setArcMetrics( Q::Fill | SmallSize, startAngle, spanAngle, 1.5 );
setArcMetrics( Q::Fill | NormalSize, startAngle, spanAngle, 3 );
setArcMetrics( Q::Fill | LargeSize, startAngle, spanAngle, 6 );
}
void Editor::setupProgressRingColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskProgressRing;
const auto& pal = theme.palette;
setGradient( Q::Fill | section, pal.fillColor.accent.defaultColor );
}
void Editor::setupPushButtonMetrics()
{
using Q = QskPushButton;
using A = QskAspect;
using W = QskFluent2Skin;
const qreal h = 32_px;
setStrutSize( Q::Panel, { 120_px, h } );
// 60: not covered by any spec, but 120 is too big for a menu bar
setStrutSize( Q::Panel | A::Header, { 60_px, h } );
setStrutSize( Q::Panel | A::Footer, { 60_px, h } );
setBoxShape( Q::Panel, 4_px );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxBorderMetrics( Q::Panel | W::Accent | Q::Disabled, 0 );
// Fluent buttons don't really have icons,
setStrutSize( Q::Icon, 12_px, 12_px );
setPadding( Q::Icon, { 0, 0, 8_px, 0 } );
setFontRole( Q::Text, Fluent2::Body );
}
void Editor::setupPushButtonColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskPushButton;
using W = QskFluent2Skin;
const auto& pal = theme.palette;
for ( const auto variation : { QskAspect::NoVariation, W::Accent } )
{
const auto panel = Q::Panel | section | variation;
const auto text = Q::Text | section | variation;
const auto icon = Q::Icon | section | variation;
for ( const auto state : { QskAspect::NoState, Q::Hovered, Q::Pressed, Q::Checked, Q::Disabled } )
{
QRgb panelColor, borderColor1, borderColor2, textColor;
int graphicRole;
if ( variation == W::Accent )
{
if ( state == Q::Hovered )
{
panelColor = pal.fillColor.accent.secondary;
borderColor1 = pal.elevation.accentControl.border[0];
borderColor2 = pal.elevation.accentControl.border[1];
textColor = pal.fillColor.textOnAccent.primary;
graphicRole = W::GraphicRoleFillColorTextOnAccentPrimary;
}
else if ( state == Q::Pressed || state == Q::Checked )
{
panelColor = pal.fillColor.accent.tertiary;
borderColor1 = borderColor2 = pal.strokeColor.control.onAccentDefault;
textColor = pal.fillColor.textOnAccent.secondary;
graphicRole = W::GraphicRoleFillColorTextOnAccentSecondary;
}
else if ( state == Q::Disabled )
{
panelColor = pal.fillColor.accent.disabled;
borderColor1 = borderColor2 = panelColor; // irrelevant: width is 0
textColor = pal.fillColor.textOnAccent.disabled;
graphicRole = W::GraphicRoleFillColorTextOnAccentDisabled;
}
else
{
panelColor = pal.fillColor.accent.defaultColor;
borderColor1 = pal.elevation.accentControl.border[0];
borderColor2 = pal.elevation.accentControl.border[1];
textColor = pal.fillColor.textOnAccent.primary;
graphicRole = W::GraphicRoleFillColorTextOnAccentPrimary;
}
}
else
{
if ( state == Q::Hovered )
{
panelColor = pal.fillColor.control.secondary;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[1];
textColor = pal.fillColor.text.primary;
graphicRole = W::GraphicRoleFillColorTextPrimary;
}
else if ( state == Q::Pressed || state == Q::Checked )
{
panelColor = pal.fillColor.control.tertiary;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
textColor = pal.fillColor.text.secondary;
graphicRole = W::GraphicRoleFillColorTextSecondary;
}
else if ( state == Q::Disabled )
{
panelColor = pal.fillColor.control.disabled;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
textColor = pal.fillColor.text.disabled;
graphicRole = W::GraphicRoleFillColorTextDisabled;
}
else
{
panelColor = pal.fillColor.control.defaultColor;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[0];
textColor = pal.fillColor.text.primary;
graphicRole = W::GraphicRoleFillColorTextPrimary;
}
}
panelColor = rgbSolid( panelColor, pal.background.solid.base );
setGradient( panel | state, panelColor );
setBoxBorderGradient( panel | state,
borderColor1, borderColor2, panelColor );
setColor( text | state, textColor );
setGraphicRole( icon | state, graphicRole );
}
}
}
void Editor::setupRadioBoxMetrics()
{
using Q = QskRadioBox;
setSpacing( Q::Button, 8_px );
setStrutSize( Q::Button, { 115_px, 38_px } );
/*
We do not have an indicator - states are indicated by the panel border
However the colors of the inner side of the border are not solid for
the selected states and we use a dummy indicator to get this done.
How to solve this in a better way, TODO ...
*/
setBoxShape( Q::CheckIndicator, 100, Qt::RelativeSize );
setBoxBorderMetrics( Q::CheckIndicator, 0 );
setBoxBorderMetrics( Q::CheckIndicator | Q::Selected, 1_px );
setBoxBorderMetrics( Q::CheckIndicator | Q::Pressed | Q::Selected, 1_px );
setBoxShape( Q::CheckIndicatorPanel, 100, Qt::RelativeSize );
setStrutSize( Q::CheckIndicatorPanel, { 20_px, 20_px } );
setBoxBorderMetrics( Q::CheckIndicatorPanel, 1_px );
setBoxBorderMetrics( Q::CheckIndicatorPanel | Q::Selected, 0 );
setPadding( Q::CheckIndicatorPanel | Q::Selected, { 5_px, 5_px } ); // indicator "strut size"
setPadding( Q::CheckIndicatorPanel | Q::Hovered | Q::Selected, { 4_px, 4_px } ); // indicator "strut size"
setPadding( Q::CheckIndicatorPanel | Q::Pressed, { 7_px, 7_px } ); // indicator "strut size"
setBoxBorderMetrics( Q::CheckIndicatorPanel | Q::Pressed | Q::Selected, 0 );
setPadding( Q::CheckIndicatorPanel | Q::Pressed | Q::Selected, { 6_px, 6_px } ); // indicator "strut size"
setBoxBorderMetrics( Q::CheckIndicatorPanel | Q::Disabled | Q::Selected, 0 );
setPadding( Q::CheckIndicatorPanel | Q::Disabled | Q::Selected, { 6_px, 6_px } ); // indicator "strut size"
setFontRole( Q::Text, Fluent2::Body );
}
void Editor::setupRadioBoxColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskRadioBox;
using A = QskAspect;
const auto& pal = theme.palette;
for ( const auto state1 : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
{
for ( const auto state2 : { A::NoState, Q::Selected } )
{
const auto states = state1 | state2;
auto indicatorColor = pal.fillColor.textOnAccent.primary;
if ( !( states & Q::Selected ) )
indicatorColor = QskRgb::toTransparent( indicatorColor, 0 );
auto textColor = pal.fillColor.text.primary;
if ( states & Q::Disabled )
textColor = pal.fillColor.text.disabled;
QRgb panelBorderColor;
if ( states & ( Q::Disabled | Q::Pressed ) )
panelBorderColor = pal.strokeColor.controlStrong.disabled;
else
panelBorderColor = pal.strokeColor.controlStrong.defaultColor;
auto panelColor = pal.fillColor.accent.defaultColor;
if ( states == A::NoState )
{
panelColor = pal.fillColor.controlAlt.secondary;
}
else if ( states == Q::Selected )
{
}
else if ( states == Q::Hovered )
{
panelColor = pal.fillColor.controlAlt.tertiary;
}
else if ( states == ( Q::Hovered | Q::Selected ) )
{
panelColor = pal.fillColor.accent.secondary;
}
else if ( states == Q::Pressed )
{
panelColor = pal.fillColor.controlAlt.quaternary;
}
else if ( states == ( Q::Pressed | Q::Selected ) )
{
panelColor = pal.fillColor.accent.tertiary;
}
else if ( states == Q::Disabled )
{
panelColor = pal.fillColor.controlAlt.disabled;
}
else if ( states == ( Q::Disabled | Q::Selected ) )
{
panelColor = pal.fillColor.accent.disabled;
}
const auto panel = Q::CheckIndicatorPanel | section | states;
const auto indicator = Q::CheckIndicator | section | states;
const auto text = Q::Text | section | states;
#if 0
// we have different colors when making colors solid early. TODO ...
panelColor = rgbSolid2( panelColor, pal.background.solid.base );
indicatorColor = rgbSolid2( indicatorColor, pal.background.solid.base );
#endif
setBoxBorderGradient( indicator, pal.elevation.circle.border, panelColor );
setGradient( panel, panelColor );
setBoxBorderColors( panel, panelBorderColor );
setGradient( indicator, indicatorColor );
setColor( text, textColor );
}
}
}
void Editor::setupScrollViewMetrics()
{
using A = QskAspect;
using Q = QskScrollView;
for ( auto subControl : { Q::HorizontalScrollBar, Q::VerticalScrollBar } )
{
setMetric( subControl | A::Size, 6_px );
// The scrollbar is expanding, when being hovered/pressed
const qreal padding = 4_px;
if ( subControl == Q::HorizontalScrollBar )
setPadding( Q::HorizontalScrollBar, 0, padding, 0, 0 );
else
setPadding( Q::VerticalScrollBar, padding, 0, 0, 0 );
setPadding( subControl | Q::Hovered, 0 );
setPadding( subControl | Q::Pressed, 0 );
setBoxShape( subControl, 100, Qt::RelativeSize );
setAnimation( subControl | A::Metric, 100 );
}
/*
The scroll bars are actually above the viewport, what is not
supported by the skinlet. Do we want to have this. TODO ...
*/
// handles
setBoxShape( Q::HorizontalScrollHandle, 100, Qt::RelativeSize );
setBoxShape( Q::VerticalScrollHandle, 100, Qt::RelativeSize );
const auto handleExtent = 40_px;
setStrutSize( Q::HorizontalScrollHandle, handleExtent, 0.0 );
setStrutSize( Q::VerticalScrollHandle, 0.0, handleExtent );
}
void Editor::setupScrollViewColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using A = QskAspect;
using Q = QskScrollView;
const auto& pal = theme.palette;
{
const auto fillColor = pal.fillColor.controlStrong.defaultColor;
setGradient( Q::HorizontalScrollHandle | section, fillColor );
setGradient( Q::VerticalScrollHandle | section, fillColor );
}
for ( auto subControl : { Q::HorizontalScrollBar, Q::VerticalScrollBar } )
{
auto fillColor = pal.fillColor.acrylic.background;
#if 1
/*
With Fluent2 the scroll bar is supposed to be on top of scrollable
item. QskScrollViewSkinlet does not support this yet and we
always have the scrollbar on top of the panel. For the light
scheme this leads to white on white, so we better shade the scrollbar
for the moment: TODO ...
*/
const auto v = qBlue( fillColor );
if ( v > 250 )
{
if ( v == qRed( fillColor ) && v == qGreen( fillColor ) )
fillColor = qRgba( 240, 240, 240, qAlpha( fillColor ) );
}
#endif
setGradient( subControl, QskRgb::toTransparent( fillColor, 0 ) );
setGradient( subControl | Q::Hovered, fillColor );
setGradient( subControl | Q::Pressed, fillColor );
setAnimation( subControl | A::Color, 100 );
}
}
void Editor::setupSegmentedBarMetrics()
{
using Q = QskSegmentedBar;
using A = QskAspect;
const QSizeF segmentStrutSize( 120_px, 32_px );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxBorderMetrics( Q::Panel | Q::Selected | Q::Disabled, 0 );
setSpacing( Q::Panel, 8_px );
setStrutSize( Q::Icon, { 12_px, 12_px } );
setFontRole( Q::Text, Fluent2::Body );
setStrutSize( Q::Segment | A::Horizontal, segmentStrutSize );
setStrutSize( Q::Segment | A::Vertical, segmentStrutSize.transposed() );
setBoxShape( Q::Segment, 4_px );
setPadding( Q::Segment, { 8_px, 0, 8_px, 0 } );
}
void Editor::setupSegmentedBarColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskSegmentedBar;
using A = QskAspect;
using W = QskFluent2Skin;
const auto& pal = theme.palette;
auto panelColor = pal.fillColor.control.defaultColor;
panelColor = rgbSolid( panelColor, pal.background.solid.base );
setGradient( Q::Panel, panelColor );
for ( const auto state1 : { A::NoState, Q::Hovered, Q::Disabled } )
{
for ( const auto state2 : { A::NoState, Q::Selected } )
{
const auto states = state1 | state2;
QRgb segmentColor, borderColor1, borderColor2, textColor;
int graphicRole;
if ( states == A::NoState )
{
segmentColor = pal.fillColor.control.defaultColor;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[1];
textColor = pal.fillColor.text.primary;
graphicRole = W::GraphicRoleFillColorTextPrimary;
}
else if ( states == Q::Hovered )
{
segmentColor = pal.fillColor.control.secondary;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[1];
textColor = pal.fillColor.text.primary;
graphicRole = W::GraphicRoleFillColorTextPrimary;
}
else if ( states == ( Q::Selected | Q::Disabled ) )
{
segmentColor = pal.fillColor.accent.disabled;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
textColor = pal.fillColor.textOnAccent.disabled;
graphicRole = W::GraphicRoleFillColorTextOnAccentDisabled;
}
else if ( states & Q::Selected )
{
segmentColor = pal.fillColor.accent.defaultColor;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[1];
textColor = pal.fillColor.textOnAccent.primary;
graphicRole = W::GraphicRoleFillColorTextOnAccentPrimary;
}
else if ( states & Q::Disabled )
{
segmentColor = pal.fillColor.control.disabled;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
textColor = pal.fillColor.text.disabled;
graphicRole = W::GraphicRoleFillColorTextDisabled;
}
const auto segment = Q::Segment | section | states;
const auto text = Q::Text | section | states;
const auto icon = Q::Icon | section | states;
segmentColor = rgbSolid( segmentColor, pal.background.solid.base );
setGradient( segment, segmentColor );
setBoxBorderGradient( segment, borderColor1, borderColor2, panelColor );
setColor( text, textColor );
setGraphicRole( icon, graphicRole );
}
}
}
void Editor::setupSeparatorMetrics()
{
using Q = QskSeparator;
using A = QskAspect;
setMetric( Q::Panel | A::Size, 1_px );
setBoxShape( Q::Panel, 0 );
setBoxBorderMetrics( Q::Panel, 0 );
}
void Editor::setupSeparatorColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskSeparator;
const auto& pal = theme.palette;
setGradient( Q::Panel | section, pal.strokeColor.divider.defaultColor );
}
void Editor::setupSliderMetrics()
{
using Q = QskSlider;
using A = QskAspect;
const qreal extent = 22_px;
setMetric( Q::Panel | A::Size, extent );
setBoxShape( Q::Panel, 0 );
setBoxBorderMetrics( Q::Panel, 0 );
setPadding( Q::Panel | A::Horizontal, QskMargins( 0.5 * extent, 0 ) );
setPadding( Q::Panel | A::Vertical, QskMargins( 0, 0.5 * extent ) );
for ( auto subControl : { Q::Groove, Q::Fill } )
{
setMetric( subControl | A::Size, 4_px );
setBoxShape( subControl, 100, Qt::RelativeSize );
}
const auto shadowSpread = 1_px;
setStrutSize( Q::Handle, { extent - shadowSpread, extent - shadowSpread } );
setBoxShape( Q::Handle, 100, Qt::RelativeSize );
setShadowMetrics( Q::Handle, { shadowSpread, 0.0 } );
setBoxBorderMetrics( Q::Handle, 5_px );
setBoxBorderMetrics( Q::Handle | Q::Hovered, 4_px );
setBoxBorderMetrics( Q::Handle | Q::Pressed, 6_px );
setBoxBorderMetrics( Q::Handle | Q::Disabled, 6_px );
setFlag( Q::Tick | A::Option, Qsk::Maybe );
setStrutSize( Q::Tick | A::Horizontal, 1_px, -1 );
setStrutSize( Q::Tick | A::Vertical, -1, 1_px );
setAnimation( Q::Handle | A::Metric | A::Position, 100 );
}
void Editor::setupSliderColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskSlider;
using A = QskAspect;
const auto& pal = theme.palette;
const auto borderColor = pal.fillColor.controlSolid.defaultColor;
setBoxBorderColors( Q::Handle, borderColor );
{
const auto gradient = pal.elevation.circle.border;
#if 1
/*
we can't set gradients as shadows - using the color, that
lies in the center of the gradient instead. TODO ...
*/
auto shadowColor = QskRgb::interpolated( gradient[0], gradient[1], 0.5 );
shadowColor = rgbFlattened( shadowColor, borderColor );
#endif
setShadowColor( Q::Handle, rgbFlattened( shadowColor, borderColor ) );
}
for ( auto state : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
{
QRgb grooveColor, fillColor, handleColor;
if ( state == A::NoState || state == Q::Hovered )
{
grooveColor = pal.fillColor.controlStrong.defaultColor;
fillColor = pal.fillColor.accent.defaultColor;
handleColor = pal.fillColor.accent.defaultColor;
}
else if ( state == Q::Pressed )
{
grooveColor = pal.fillColor.controlStrong.defaultColor;
handleColor = pal.fillColor.accent.tertiary;
fillColor = pal.fillColor.accent.defaultColor;
}
else if ( state == Q::Disabled )
{
grooveColor = pal.fillColor.controlStrong.disabled;
fillColor = pal.fillColor.accent.disabled;
handleColor = grooveColor;
}
grooveColor = rgbSolid( grooveColor, pal.background.solid.base );
setGradient( Q::Groove | section | state, grooveColor );
setGradient( Q::Fill | section | state, fillColor );
setGradient( Q::Handle | section | state, handleColor );
}
setGradient( Q::Tick, pal.fillColor.controlSolid.defaultColor );
}
void Editor::setupSpinBoxMetrics()
{
using Q = QskSpinBox;
/*
The F2 NumberBox has 2 modes for the Up/Down panels ( a.k.a Spinner ):
- compact ( -> QskSpinBox::UpDownControl )
- inline ( -> QskSpinBox::ButtonsRight )
TODO: in compact mode the panels/indicators are blown up, when being hovered
*/
setHint( Q::Panel | QskAspect::Style, Q::ButtonsRight );
setStrutSize( Q::Panel, { -1, 32_px } );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxShape( Q::Panel, 3_px );
setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Text, Fluent2::Body );
setPadding( Q::TextPanel, { 11_px, 0, 11_px, 0 } );
for ( auto panel : { Q::UpPanel, Q::DownPanel } )
setStrutSize( panel, 32_px, 18_px );
setSymbol( Q::UpIndicator, symbol( "chevron_up" ) );
setSymbol( Q::DownIndicator, symbol( "chevron_down" ) );
setPadding( Q::UpPanel, { 0, 1_px, 0, 0 } );
setPadding( Q::DownPanel, { 0, 0, 0, 1_px } );
#if 0
// QskSpinBox::Pressed is missing yet
setBoxBorderMetrics( Q::Panel | Q::Pressed, { 1_px, 1_px, 1_px, 2_px } );
#endif
}
void Editor::setupSpinBoxColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskSpinBox;
using A = QskAspect;
const auto& pal = theme.palette;
for ( auto state : { A::NoState, Q::Hovered, Q::Focused, Q::Disabled } )
{
QRgb panelColor, borderColor1, borderColor2;
if ( state == A::NoState )
{
panelColor = pal.fillColor.control.defaultColor;
borderColor1 = pal.elevation.control.border[0];
borderColor2 = pal.elevation.control.border[1];
}
else if ( state == Q::Hovered )
{
panelColor = pal.fillColor.control.secondary;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.elevation.textControl.border[1];
}
else if ( state == Q::Focused )
{
// Focused (Pressed doesn't exist yet):
panelColor = pal.fillColor.control.inputActive;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.fillColor.accent.defaultColor;
}
else if ( state == Q::Disabled )
{
panelColor = pal.fillColor.control.disabled;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
}
QRgb textColor;
int graphicRole;
if ( state != Q::Disabled )
{
textColor = pal.fillColor.text.primary;
graphicRole = QskFluent2Skin::GraphicRoleFillColorTextSecondary;
}
else
{
textColor = pal.fillColor.text.disabled;
graphicRole = QskFluent2Skin::GraphicRoleFillColorTextDisabled;
}
const auto panel = Q::Panel | section | state;
const auto text = Q::Text | section | state;
const auto upIndicator = Q::UpIndicator | section | state;
const auto downIndicator = Q::DownIndicator | section | state;
panelColor = rgbSolid( panelColor, pal.background.solid.base );
setGradient( panel, panelColor );
setBoxBorderGradient( panel, borderColor1, borderColor2, panelColor );
if ( state == Q::Focused )
{
const auto colors = boxBorderColors( panel );
setBoxBorderColors( panel | Q::Decreasing, colors );
setBoxBorderColors( panel | Q::Increasing, colors );
}
setColor( text, textColor );
setGraphicRole( upIndicator, graphicRole );
setGraphicRole( downIndicator, graphicRole );
}
}
void Editor::setupTabBarMetrics()
{
}
void Editor::setupTabBarColors( QskAspect::Section section, const QskFluent2Theme& theme )
{
setGradient( QskTabBar::Panel | section, theme.palette.background.solid.base );
}
void Editor::setupTabButtonMetrics()
{
using Q = QskTabButton;
setStrutSize( Q::Panel, { -1, 31_px } );
setPadding( Q::Panel, { 7_px, 0, 7_px, 0 } );
setBoxShape( Q::Panel, { 7_px, 7_px, 0, 0 } );
setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter );
setBoxBorderMetrics( Q::Panel, { 0, 0, 0, 1_px } );
setBoxBorderMetrics( Q::Panel | Q::Checked, { 1_px, 1_px, 1_px, 0 } );
setFontRole( Q::Text, Fluent2::Body );
setFontRole( Q::Text | Q::Checked, Fluent2::BodyStrong );
}
void Editor::setupTabButtonColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskTabButton;
const auto& pal = theme.palette;
for ( const auto state : { QskAspect::NoState,
Q::Checked, Q::Hovered, Q::Pressed, Q::Disabled } )
{
QRgb panelColor, textColor;
if ( state == Q::Checked )
{
panelColor = pal.background.solid.secondary;
textColor = pal.fillColor.text.primary;
}
else if ( state == Q::Hovered )
{
panelColor = pal.fillColor.subtle.secondary;
textColor = pal.fillColor.text.secondary;
}
else if ( state == Q::Pressed )
{
panelColor = pal.fillColor.subtle.tertiary;
textColor = pal.fillColor.text.secondary;
}
else if ( state == Q::Disabled )
{
panelColor = pal.fillColor.control.disabled;
textColor = pal.fillColor.text.disabled;
}
else
{
panelColor = pal.fillColor.subtle.tertiary;
textColor = pal.fillColor.text.secondary;
}
const auto panel = Q::Panel | section | state;
const auto text = Q::Text | section | state;
panelColor = rgbSolid( panelColor, pal.background.solid.base );
setGradient( panel, panelColor );
const auto borderColor = rgbSolid(
pal.strokeColor.card.defaultColor, pal.background.solid.base );
setBoxBorderColors( panel, borderColor );
setColor( text, textColor );
}
}
void Editor::setupTabViewMetrics()
{
}
void Editor::setupTabViewColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskTabView;
setGradient( Q::Page | section, theme.palette.background.solid.secondary );
}
void Editor::setupGraphicLabelMetrics()
{
using Q = QskGraphicLabel;
setPadding( Q::Panel, 10_px );
setBoxShape( Q::Panel, 3_px );
setBoxBorderMetrics( Q::Panel, 1_px );
}
void Editor::setupGraphicLabelColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskGraphicLabel;
const auto& pal = theme.palette;
#if 1
setColor( Q::Panel | section, pal.fillColor.subtle.secondary );
setBoxBorderColors( Q::Panel | section, pal.strokeColor.control.defaultColor );
#endif
}
void Editor::setupTextLabelMetrics()
{
using Q = QskTextLabel;
setPadding( Q::Panel, 10_px );
setBoxShape( Q::Panel, 3_px );
setBoxBorderMetrics( Q::Panel, 1_px );
setFontRole( Q::Text, Fluent2::Body );
}
void Editor::setupTextLabelColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskTextLabel;
const auto& pal = theme.palette;
#if 1
setColor( Q::Panel | section, pal.fillColor.subtle.secondary );
setBoxBorderColors( Q::Panel | section, pal.strokeColor.control.defaultColor );
#endif
setColor( Q::Text | section, pal.fillColor.text.primary );
}
void Editor::setupTextFieldMetrics()
{
using Q = QskTextField;
setStrutSize( Q::Panel, { -1, 30_px } );
setPadding( Q::Panel, { 11_px, 0, 11_px, 0 } );
setBoxBorderMetrics( Q::Panel, 1_px );
for( const auto& state : { Q::Focused, Q::Editing } )
setBoxBorderMetrics( Q::Panel | state, { 1_px, 1_px, 1_px, 2_px } );
setBoxShape( Q::Panel, 3_px );
setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter );
setFontRole( Q::Text, Fluent2::Body );
setAlignment( Q::PlaceholderText, alignment( Q::Text ) );
setFontRole( Q::PlaceholderText, fontRole( Q::Text ) );
}
void Editor::setupTextFieldColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskTextField;
using SK = QskTextFieldSkinlet;
using A = QskAspect;
const auto& pal = theme.palette;
setColor( Q::Panel | SK::Selected, pal.fillColor.accent.selectedTextBackground );
setColor( Q::Text | SK::Selected, pal.fillColor.textOnAccent.selectedText );
setColor( Q::PlaceholderText, pal.fillColor.text.secondary );
for( const auto state : { A::NoState, Q::Hovered, Q::Focused, Q::Editing, Q::Disabled } )
{
QRgb panelColor, borderColor1, borderColor2, textColor;
if ( state == A::NoState )
{
panelColor = pal.fillColor.control.defaultColor;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.elevation.textControl.border[1];
textColor = pal.fillColor.text.primary;
}
else if ( state == Q::Hovered )
{
panelColor = pal.fillColor.control.secondary;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.elevation.textControl.border[1];
textColor = pal.fillColor.text.primary;
}
else if ( ( state == Q::Focused ) || ( state == Q::Editing ) )
{
panelColor = pal.fillColor.control.inputActive;
borderColor1 = pal.elevation.textControl.border[0];
borderColor2 = pal.fillColor.accent.defaultColor;
textColor = pal.fillColor.text.primary;
}
else if ( state == Q::Disabled )
{
panelColor = pal.fillColor.control.disabled;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
textColor = pal.fillColor.text.disabled;
}
const auto panel = Q::Panel | section | state;
const auto text = Q::Text | section | state;
panelColor = rgbSolid( panelColor, pal.background.solid.base );
setGradient( panel, panelColor );
setBoxBorderGradient( panel, borderColor1, borderColor2, panelColor );
setColor( text, textColor );
}
}
void Editor::setupSwitchButtonMetrics()
{
using Q = QskSwitchButton;
using A = QskAspect;
const QSizeF strutSize( 38_px, 18_px );
setStrutSize( Q::Groove | A::Horizontal, strutSize );
setStrutSize( Q::Groove | A::Vertical, strutSize.transposed() );
setBoxShape( Q::Groove, 100, Qt::RelativeSize );
setBoxBorderMetrics( Q::Groove, 1_px );
setBoxBorderMetrics( Q::Groove | Q::Checked, 0 );
setBoxShape( Q::Handle, 100, Qt::RelativeSize );
setPosition( Q::Handle, 0.1, { QskStateCombination::CombinationNoState, Q::Disabled } );
setPosition( Q::Handle | Q::Checked, 0.9,
{ QskStateCombination::CombinationNoState, Q::Disabled } );
setBoxBorderMetrics( Q::Handle, 0 );
setBoxBorderMetrics( Q::Handle | Q::Checked, 1_px );
setBoxBorderMetrics( Q::Handle | Q::Disabled | Q::Checked, 0 );
setStrutSize( Q::Handle, 12_px, 12_px );
setStrutSize( Q::Handle | Q::Hovered, 14_px, 14_px,
{ QskStateCombination::CombinationNoState, Q::Checked } );
const QSizeF pressedSize( 17_px, 14_px );
setStrutSize( Q::Handle | Q::Pressed | A::Horizontal,
pressedSize, { QskStateCombination::CombinationNoState, Q::Checked } );
setStrutSize( Q::Handle | Q::Pressed | A::Vertical,
pressedSize.transposed(), { QskStateCombination::CombinationNoState, Q::Checked } );
setStrutSize( Q::Handle | Q::Disabled, 12_px, 12_px,
{ QskStateCombination::CombinationNoState, Q::Checked } );
setAnimation( Q::Handle | A::Metric, 100 );
}
void Editor::setupSwitchButtonColors(
QskAspect::Section section, const QskFluent2Theme& theme )
{
using Q = QskSwitchButton;
using A = QskAspect;
const auto& pal = theme.palette;
for ( const auto state1 : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
{
for ( const auto state2 : { A::NoState, Q::Checked } )
{
const auto states = state1 | state2;
QRgb grooveColor, grooveBorderColor, handleColor;
if ( states == A::NoState )
{
grooveColor = pal.fillColor.controlAlt.secondary;
grooveBorderColor = pal.strokeColor.controlStrong.defaultColor;
handleColor = pal.strokeColor.controlStrong.defaultColor;
}
else if ( states == Q::Checked )
{
grooveColor = pal.fillColor.accent.defaultColor;
grooveBorderColor = pal.strokeColor.controlStrong.defaultColor;
handleColor = pal.fillColor.textOnAccent.primary;
}
else if ( states == Q::Hovered )
{
grooveColor = pal.fillColor.controlAlt.tertiary;
grooveBorderColor = pal.fillColor.text.secondary;
handleColor = pal.fillColor.text.secondary;
}
else if ( states == ( Q::Hovered | Q::Checked ) )
{
grooveColor = pal.fillColor.accent.secondary;
grooveBorderColor = pal.strokeColor.controlStrong.defaultColor;
handleColor = pal.fillColor.textOnAccent.primary;
//handleColor = pal.fillColor.accent.defaultColor;
}
else if ( states == Q::Pressed )
{
grooveColor = pal.fillColor.controlAlt.quaternary;
grooveBorderColor = pal.strokeColor.controlStrong.defaultColor;
handleColor = pal.strokeColor.controlStrong.defaultColor;
}
else if ( states == ( Q::Pressed | Q::Checked ) )
{
grooveColor = pal.fillColor.accent.tertiary;
grooveBorderColor = pal.strokeColor.controlStrong.defaultColor;
handleColor = pal.fillColor.textOnAccent.primary;
}
else if ( states == Q::Disabled )
{
grooveColor = pal.fillColor.controlAlt.disabled;
grooveBorderColor = pal.fillColor.text.disabled;
handleColor = pal.fillColor.text.disabled;
}
else if ( states == ( Q::Disabled | Q::Checked ) )
{
grooveColor = pal.fillColor.accent.disabled;
grooveBorderColor = pal.fillColor.accent.disabled;
handleColor = pal.fillColor.textOnAccent.disabled;
}
const auto groove = Q::Groove | section | states;
const auto handle = Q::Handle | section | states;
grooveColor = rgbSolid( grooveColor, pal.background.solid.base );
setGradient( groove, grooveColor );
setBoxBorderColors( groove, grooveBorderColor );
setGradient( handle, handleColor );
setBoxBorderGradient( handle, pal.elevation.circle.border, grooveColor );
}
}
}
void Editor::setupSubWindow( const QskFluent2Theme& theme )
{
using Q = QskSubWindow;
using A = QskAspect;
const auto& pal = theme.palette;
setBoxShape( Q::Panel, 7_px );
setBoxBorderMetrics( Q::Panel, 1_px );
setBoxBorderColors( Q::Panel, pal.strokeColor.surface.defaultColor );
setGradient( Q::Panel, pal.background.layer.alt );
setShadowMetrics( Q::Panel, theme.shadow.dialog.metrics );
setShadowColor( Q::Panel, theme.shadow.dialog.color );
setHint( Q::TitleBarPanel | QskAspect::Style, Q::NoDecoration );
setPadding( Q::TitleBarPanel, { 24_px, 31_px, 24_px, 0 } );
setFontRole( Q::TitleBarText, Fluent2::Subtitle );
setColor( Q::TitleBarText, pal.fillColor.text.primary );
setAlignment( Q::TitleBarText, Qt::AlignLeft );
setTextOptions( Q::TitleBarText, Qt::ElideRight, QskTextOptions::NoWrap );
setAnimation( Q::Panel | A::Position, 300, QEasingCurve::OutCubic );
}
void Editor::setupDialogSubWindow( const QskFluent2Theme& )
{
using Q = QskDialogSubWindow;
setFontRole( Q::DialogTitle, Fluent2::Subtitle );
setAlignment( Q::DialogTitle, Qt::AlignLeft | Qt::AlignVCenter );
setTextOptions( Q::DialogTitle, Qt::ElideRight, QskTextOptions::WordWrap );
}
void Editor::setupVirtualKeyboardMetrics()
{
using Q = QskVirtualKeyboard;
setMargin( Q::ButtonPanel, 2_px );
setFontRole( Q::ButtonText, Fluent2::BodyStrong );
setPadding( Q::Panel, 8_px );
}
void Editor::setupVirtualKeyboardColors(
QskAspect::Section, const QskFluent2Theme& theme )
{
using Q = QskVirtualKeyboard;
const auto& pal = theme.palette;
setGradient( Q::ButtonPanel, pal.fillColor.control.defaultColor );
setGradient( Q::ButtonPanel | Q::Hovered, pal.fillColor.control.secondary );
setGradient( Q::ButtonPanel | QskPushButton::Pressed, pal.fillColor.control.tertiary );
setColor( Q::ButtonText, pal.fillColor.text.primary );
setColor( Q::ButtonText | QskPushButton::Pressed, pal.fillColor.text.secondary );
setGradient( Q::Panel, pal.background.solid.tertiary );
}
QskFluent2Skin::QskFluent2Skin( QObject* parent )
: Inherited( parent )
{
declareSkinlet< QskTextField, QskFluent2TextFieldSkinlet >();
setupFonts();
Editor editor( &hintTable() );
editor.setupMetrics();
}
void QskFluent2Skin::addTheme( QskAspect::Section section, const QskFluent2Theme& theme )
{
if ( section == QskAspect::Body )
{
// design flaw: we can't have section sensitive filters. TODO ..
setupGraphicFilters( theme );
}
Editor editor( &hintTable() );
editor.setupColors( section, theme );
}
QskFluent2Skin::~QskFluent2Skin()
{
}
void QskFluent2Skin::initHints()
{
struct
{
QskFluent2Theme::BaseColors baseColors;
QskFluent2Theme::AccentColors accentColors;
} colors[2];
if( colorScheme() != QskSkin::DarkScheme )
{
colors[0].baseColors = { rgbGray( 243 ), rgbGray( 249 ), rgbGray( 238 ) };
colors[0].accentColors = { 0xff0078d4, 0xff005eb7, 0xff003d92, 0xff001968 };
colors[1].baseColors = { rgbGray( 249 ), rgbGray( 249 ), rgbGray( 238 ) };
colors[1].accentColors = colors[0].accentColors;
}
else
{
colors[0].baseColors = { rgbGray( 32 ), rgbGray( 40 ), rgbGray( 28 ) };
colors[0].accentColors = { 0xff0078d4, 0xff0093f9, 0xff60ccfe, 0xff98ecfe };
colors[1].baseColors = { rgbGray( 40 ), rgbGray( 44 ), rgbGray( 28 ) };
colors[1].accentColors = colors[0].accentColors;
}
setupFonts();
Editor editor( &hintTable() );
editor.setupMetrics();
const QskFluent2Theme themeBody( colorScheme(),
colors[0].baseColors, colors[0].accentColors );
const QskFluent2Theme themeHeader( colorScheme(),
colors[1].baseColors, colors[1].accentColors );
addTheme( QskAspect::Body, themeBody );
addTheme( QskAspect::Header, themeHeader );
addTheme( QskAspect::Footer, themeHeader );
}
static inline QFont createFont( qreal size, int lineHeight, QFont::Weight weight )
{
Q_UNUSED( lineHeight ); // TODO ...
QFont font( QStringLiteral( "Segoe UI" ), -1, weight );
static bool checkFont = true;
if ( checkFont )
{
const QFontInfo info( font );
if ( info.family() != font.family() )
{
qWarning() << font.family() <<
"not found, using" << info.family() << "instead.";
}
checkFont = false;
}
// px: 1/96 inch, pt: 1/72 inch
font.setPointSizeF( size * 72.0 / 96.0 );
return font;
}
void QskFluent2Skin::setupFonts()
{
// see: https://fluent2.microsoft.design/typography ( Windows )
setFont( Fluent2::Caption, createFont( 12_px, 16_px, QFont::Normal ) );
setFont( Fluent2::Body, createFont( 14_px, 20_px, QFont::Normal ) );
setFont( Fluent2::BodyStrong, createFont( 14_px, 20_px, QFont::DemiBold ) );
setFont( Fluent2::BodyStronger, createFont( 18_px, 24_px, QFont::Normal ) );
setFont( Fluent2::Subtitle, createFont( 20_px, 28_px, QFont::DemiBold ) );
setFont( Fluent2::Title, createFont( 28_px, 36_px, QFont::Normal ) );
setFont( Fluent2::LargeTitle, createFont( 40_px, 52_px, QFont::DemiBold ) );
setFont( Fluent2::Display, createFont( 68_px, 92_px, QFont::DemiBold ) );
// to have something for the unused roles
QskSkin::completeFontTable();
}
void QskFluent2Skin::setGraphicColor( GraphicRole role, QRgb rgb )
{
QskColorFilter colorFilter;
colorFilter.setMask( QskRgb::RGBAMask );
colorFilter.addColorSubstitution( QskRgb::Black, rgb );
setGraphicFilter( role, colorFilter );
}
void QskFluent2Skin::setupGraphicFilters( const QskFluent2Theme& theme )
{
const auto& colors = theme.palette.fillColor;
setGraphicColor( GraphicRoleFillColorTextDisabled, colors.text.disabled );
setGraphicColor( GraphicRoleFillColorTextOnAccentDisabled, colors.textOnAccent.disabled );
setGraphicColor( GraphicRoleFillColorTextOnAccentPrimary, colors.textOnAccent.primary );
setGraphicColor( GraphicRoleFillColorTextOnAccentSecondary, colors.textOnAccent.secondary );
setGraphicColor( GraphicRoleFillColorTextPrimary, colors.text.primary );
setGraphicColor( GraphicRoleFillColorTextSecondary, colors.text.secondary );
}
#include "moc_QskFluent2Skin.cpp"