diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..00a786d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## v1.0.0
+
+- Initial release. Enjoy!
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae104ba..bb53cc1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,16 @@ project("qlementine"
set(PROJECT_COPYRIGHT "© Olivier Cléro, MIT License.")
set(PROJECT_NAMESPACE "oclero")
+# Temporary hack to make it work with Qt6 <6.4.2 && >6.4.2 With Qt5, it was
+# "path/to/Qt/6.7.0/msvc2019_64/lib/cmake/Qt6", but with Qt6, it is now
+# "path/to/Qt/6.7.0/msvc2019_64".
+if(WIN32)
+ string(FIND "${CMAKE_PREFIX_PATH}" "/lib/cmake/Qt6" USING_Qt6_INDEX)
+ if(NOT ${USING_Qt6_INDEX} EQUAL -1)
+ string(REPLACE "/lib/cmake/Qt6" "" "${CMAKE_PREFIX_PATH}" "${CMAKE_PREFIX_PATH}")
+ endif()
+endif()
+
# Global flags.
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 17)
@@ -23,7 +33,7 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
-if (NOT CMAKE_OSX_DEPLOYMENT_TARGET)
+if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.6")
endif()
@@ -31,12 +41,14 @@ endif()
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Svg)
qt_standard_project_setup()
-# include(DeployQt)
-
# The library.
add_subdirectory(lib)
-# Sandbox.
-if(${PROJECT_IS_TOP_LEVEL})
+# Example apps using the lib.
+if(QLEMENTINE_SANDBOX)
add_subdirectory(sandbox)
endif()
+
+if(QLEMENTINE_SHOWCASE)
+ add_subdirectory(showcase)
+endif()
diff --git a/CMakePresets.json b/CMakePresets.json
index 7e24687..d18512e 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -13,7 +13,9 @@
"generator": "Xcode",
"binaryDir": "${sourceDir}/_build",
"cacheVariables": {
- "CMAKE_PREFIX_PATH": "/opt/homebrew/opt/qt/lib/cmake/Qt6"
+ "CMAKE_PREFIX_PATH": "/opt/homebrew/opt/qt/lib/cmake/Qt6",
+ "QLEMENTINE_SANDBOX": true,
+ "QLEMENTINE_SHOWCASE": true
},
"condition": {
"type": "equals",
@@ -28,7 +30,9 @@
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/_build",
"cacheVariables": {
- "CMAKE_PREFIX_PATH": "C:/Qt/6.8.0/msvc2022_64"
+ "CMAKE_PREFIX_PATH": "C:/Qt/6.8.0/msvc2022_64",
+ "QLEMENTINE_SANDBOX": true,
+ "QLEMENTINE_SHOWCASE": true
},
"condition": {
"type": "equals",
@@ -42,6 +46,10 @@
"description": "Makefile for Linux",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/_build",
+ "cacheVariables": {
+ "QLEMENTINE_SANDBOX": true,
+ "QLEMENTINE_SHOWCASE": true
+ },
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
@@ -54,9 +62,8 @@
"name": "macos",
"displayName": "macOS",
"configurePreset": "macos",
- "description": "Release build with Xcode for macOS",
+ "description": "Build with Xcode for macOS",
"targets": ["qlementine"],
- "configuration": "Release",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
@@ -67,9 +74,20 @@
"name": "macos-sandbox",
"displayName": "Sandbox for macOS",
"configurePreset": "macos",
- "description": "Sandbox - Release build with Xcode for macOS",
+ "description": "Sandbox - Build with Xcode for macOS",
"targets": ["sandbox"],
- "configuration": "Release",
+ "condition": {
+ "type": "equals",
+ "lhs": "${hostSystemName}",
+ "rhs": "Darwin"
+ }
+ },
+ {
+ "name": "macos-showcase",
+ "displayName": "Showcase for macOS",
+ "configurePreset": "macos",
+ "description": "Showcase - Build with Xcode for macOS",
+ "targets": ["showcase"],
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
@@ -80,9 +98,8 @@
"name": "windows",
"displayName": "Windows",
"configurePreset": "windows",
- "description": "Release build with Visual Studio for Windows",
+ "description": "Build with Visual Studio for Windows",
"targets": ["qlementine"],
- "configuration": "Release",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
@@ -93,9 +110,20 @@
"name": "windows-sandbox",
"displayName": "Sandbox for Windows",
"configurePreset": "windows",
- "description": "Sandbox - Release build with Visual Studio for Windows",
+ "description": "Sandbox - Build with Visual Studio for Windows",
"targets": ["sandbox"],
- "configuration": "Release",
+ "condition": {
+ "type": "equals",
+ "lhs": "${hostSystemName}",
+ "rhs": "Windows"
+ }
+ },
+ {
+ "name": "windows-showcase",
+ "displayName": "Showcase for Windows",
+ "configurePreset": "windows",
+ "description": "Showcase - Build with Visual Studio for Windows",
+ "targets": ["showcase"],
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
@@ -106,9 +134,8 @@
"name": "linux",
"displayName": "Linux",
"configurePreset": "linux",
- "description": "Release build for Linux",
+ "description": "Build for Linux",
"targets": ["qlementine"],
- "configuration": "Release",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
@@ -119,9 +146,20 @@
"name": "linux-sandbox",
"displayName": "Sandbox for Linux",
"configurePreset": "linux",
- "description": "Sandbox - Release build for Linux",
+ "description": "Sandbox - Build for Linux",
"targets": ["sandbox"],
- "configuration": "Release",
+ "condition": {
+ "type": "equals",
+ "lhs": "${hostSystemName}",
+ "rhs": "Linux"
+ }
+ },
+ {
+ "name": "linux-showcase",
+ "displayName": "Showcase for Linux",
+ "configurePreset": "linux",
+ "description": "Showcase - Build for Linux",
+ "targets": ["showcase"],
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
diff --git a/README.md b/README.md
index 84475e3..ad73cb1 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,11 @@ Modern QStyle for desktop Qt6 applications.
See [documentation](https://oclero.github.io/qlementine) for more information.
+
+
+
+
+
---
### Table of Contents
diff --git a/branding/icon/icon.psd b/branding/icon/icon.psd
deleted file mode 100644
index 58c095c..0000000
Binary files a/branding/icon/icon.psd and /dev/null differ
diff --git a/branding/screenshots/windows-dark.png b/branding/screenshots/windows-dark.png
new file mode 100644
index 0000000..32a5320
Binary files /dev/null and b/branding/screenshots/windows-dark.png differ
diff --git a/branding/screenshots/windows-light.png b/branding/screenshots/windows-light.png
new file mode 100644
index 0000000..9fbba9f
Binary files /dev/null and b/branding/screenshots/windows-light.png differ
diff --git a/docs/theme.md b/docs/theme.md
index 277569d..ac4b3e9 100644
--- a/docs/theme.md
+++ b/docs/theme.md
@@ -24,25 +24,29 @@ Not all the `Theme` struct members are customizable, because some are used as ca
### Metadata
-Metadata must be a JSON object for the key `meta`. It is not mandatory, but might be useful if a GUI to switch themes is implemented someday.
+Metadata must be a JSON object for the key `meta`.
+
+!!! info
+
+ It is mandatory if you want to use `ThemeManager`, because the theme name is used as the identifier that should be unique for every theme.
Example:
```json
{
"meta": {
- "author": "Olivier Cléro",
- "name": "Light",
+ "author": "John Doe",
+ "name": "My Awesome Theme",
"version": "1.2.3"
}
}
```
-| Key | Type | Role |
-|:----|:----:|:-----|
-| `name` | string | Name of the theme.Example: `"Light Theme"` |
-| `version` | string | Version of the theme.Example: `"1.0.0"` |
-| `author` | string | Author of the theme.Example: `"John Doe"` |
+| Key | Type | Role |
+| :-------- | :----: | :--------------------------------------------------- |
+| `name` | string | Name of the theme.Example: `"My Awesome Theme"` |
+| `version` | string | Version of the theme.Example: `"1.2.3"` |
+| `author` | string | Author of the theme.Example: `"John Doe"` |
### Colors
@@ -54,72 +58,74 @@ Example:
}
```
-| Key | Type | Role | Default |
-|:----|:----:|:-----|:--------|
-| **`backgroundColorMain1`** | color | Textfields, checkboxes, radiobuttons background. | `#ffffff` |
-| `backgroundColorMain2` | color | Window background. | `#f3f3f3` |
-| `backgroundColorMain3` | color | Container background, more contrast. | `#e3e3e3` |
-| `backgroundColorMain4` | color | Same as above, more contrast. | `#dcdcdc` |
-| **`neutralColor`** | color | Neutral interactive elements, such as buttons. | `#e1e1e1` |
-| `neutralColorHovered` | color | Same as above, in hovered state. | `#dadada` |
-| `neutralColorPressed` | color | Same as above, in pressed state. | `#d2d2d2` |
-| `neutralColorDisabled` | color | Same as above, in disabled state. | `#eeeeee` |
-| **`focusColor`** | color | Border around the widget that has keyboard focus. | `#40a9ff66` |
-| **`primaryColor`** | color | Highlighted elements (default, checked or selected). | `#1890ff` |
-| `primaryColorHovered` | color | Same as above, in hovered state. | `#2c9dff` |
-| `primaryColorPressed` | color | Same as above, in pressed state. | `#40a9ff` |
-| `primaryColorDisabled` | color | Same as above, in disabled state. | `#d1e9ff` |
-| **`primaryColorForeground`** | color | Text written over highlighted elements. | `#ffffff` |
-| `primaryColorForegroundHovered` | color | Same as above, in hovered state. | `#ffffff` |
-| `primaryColorForegroundPressed` | color | Same as above, in pressed state. | `#ffffff` |
-| `primaryColorForegroundDisabled` | color | Same as above, in disabled state. | `#ecf6ff` |
-| **`primaryAlternativeColor`** | color | A darker/lighter tint for highlighted elements over already highlighted elements. | `#106ef9` |
-| `primaryAlternativeColorHovered` | color | Same as above, in hovered state. | `#0f7bfd` |
-| `primaryAlternativeColorPressed` | color | Same as above, in pressed state. | `#0f88fd` |
-| `primaryAlternativeColorDisabled` | color | Same as above, in disabled state. | `#a9d6ff` |
-| **`secondaryColor`** | color | Text. | `#404040` |
-| `secondaryColorHovered` | color | Same as above, in hovered state. | `#333333` |
-| `secondaryColorPressed` | color | Same as above, in pressed state. | `#262626` |
-| `secondaryColorDisabled` | color | Same as above, in disabled state. | `#d4d4d4` |
-| **`secondaryColorForeground`** | color | | `#ffffff` |
-| `secondaryColorForegroundHovered` | color | Same as above, in hovered state. | `#ffffff` |
-| `secondaryColorForegroundPressed` | color | Same as above, in pressed state. | `#ffffff` |
-| `secondaryColorForegroundDisabled` | color | Same as above, in disabled state. | `#ededed` |
-| **`secondaryAlternativeColor`** | color | Less important text. | `#909090` |
-| `secondaryAlternativeColorHovered` | color | Same as above, in hovered state. | `#747474` |
-| `secondaryAlternativeColorPressed` | color | Same as above, in pressed state. | `#828282` |
-| `secondaryAlternativeColorDisabled` | color | Same as above, in disabled state. | `#c3c3c3` |
-| **`statusColorSuccess`** | color | Feedback for success/validity. | `#2bb5a0` |
-| `statusColorSuccessHovered` | color | Same as above, in hovered state. | `#3cbfab` |
-| `statusColorSuccessPressed` | color | Same as above, in pressed state. | `#4ecdb9` |
-| `statusColorSuccessDisabled` | color | Same as above, in disabled state. | `#d5f0ec` |
-| **`statusColorInfo`** | color | Feedback for information. | `#1ba8d5` |
-| `statusColorInfoHovered` | color | Same as above, in hovered state. | `#1eb5e5` |
-| `statusColorInfoPressed` | color | Same as above, in pressed state. | `#29c0f0` |
-| `statusColorInfoDisabled` | color | Same as above, in disabled state. | `#c7eaf5` |
-| **`statusColorWarning`** | color | Feedback for warning. | `#fbc064` |
-| `statusColorWarningHovered` | color | Same as above, in hovered state. | `#ffcf6c` |
-| `statusColorWarningPressed` | color | Same as above, in pressed state. | `#ffd880` |
-| `statusColorWarningDisabled` | color | Same as above, in disabled state. | `#feefd8` |
-| **`statusColorError`** | color | Feedback for error. | `#e96b72` |
-| `statusColorErrorHovered` | color | Same as above, in hovered state. | `#f47c83` |
-| `statusColorErrorPressed` | color | Same as above, in pressed state. | `#ff9197` |
-| `statusColorErrorDisabled` | color | Same as above, in disabled state. | `#f9dadc` |
-| **`statusColorForeground`** | color | Text over status colors. | `#ffffff` |
-| `statusColorForegroundHovered` | color | Same as above, in hovered state. | `#ffffff` |
-| `statusColorForegroundPressed` | color | Same as above, in pressed state. | `#ffffff` |
-| `statusColorForegroundDisabled` | color | Same as above, in disabled state. | `#ffffff99` |
-| **`shadowColor1`** | color | Shadow for elevated elements. | `#00000020` |
-| `shadowColor2` | color | Same as above, more contrast. | `#00000040` |
-| `shadowColor3` | color | Same as above, more contrast. | `#00000060` |
-| **`borderColor`** | color | Borders of textfields, checkboxes, radiobuttons, switches and other elements. | `#d3d3d3` |
-| `borderColorHovered` | color | Same as above, in hovered state. | `#b3b3b3` |
-| `borderColorPressed` | color | Same as above, in pressed state. | `#a3a3a3` |
-| `borderColorDisabled` | color | Same as above, in disabled state. | `#b3b3b3` |
-| **`semiTransparentColor1`** | color | To be used over another color to lighten/darken it. | `#0000000a` |
-| `semiTransparentColor2` | color | Same as above but more contrast. | `#00000019` |
-| `semiTransparentColor3` | color | Same as above but more contrast. | `#00000021` |
-| `semiTransparentColor4` | color | Same as above but more contrast. | `#00000028` |
+| Key | Type | Role |
+| :---------------------------------- | :---: | :-------------------------------------------------------------------------------- |
+| **`backgroundColorMain1`** | color | Textfields, checkboxes, radiobuttons background. |
+| `backgroundColorMain2` | color | Window background. |
+| `backgroundColorMain3` | color | Container background, more contrast. |
+| `backgroundColorMain4` | color | Same as above, more contrast. |
+| **`neutralColor`** | color | Neutral interactive elements, such as buttons. |
+| `neutralColorHovered` | color | Same as above, in hovered state. |
+| `neutralColorPressed` | color | Same as above, in pressed state. |
+| `neutralColorDisabled` | color | Same as above, in disabled state. |
+| **`focusColor`** | color | Border around the widget that has keyboard focus with `QFocusFrame`. |
+| **`backgroundColorWorkspace`** | color | Window workspace backround. |
+| **`backgroundColorTabBar`** | color | `QTabBar` backround. |
+| **`primaryColor`** | color | Highlighted elements (default, checked or selected). |
+| `primaryColorHovered` | color | Same as above, in hovered state. |
+| `primaryColorPressed` | color | Same as above, in pressed state. |
+| `primaryColorDisabled` | color | Same as above, in disabled state. |
+| **`primaryColorForeground`** | color | Text written over highlighted elements. |
+| `primaryColorForegroundHovered` | color | Same as above, in hovered state. |
+| `primaryColorForegroundPressed` | color | Same as above, in pressed state. |
+| `primaryColorForegroundDisabled` | color | Same as above, in disabled state. |
+| **`primaryAlternativeColor`** | color | A darker/lighter tint for highlighted elements over already highlighted elements. |
+| `primaryAlternativeColorHovered` | color | Same as above, in hovered state. |
+| `primaryAlternativeColorPressed` | color | Same as above, in pressed state. |
+| `primaryAlternativeColorDisabled` | color | Same as above, in disabled state. |
+| **`secondaryColor`** | color | Text. |
+| `secondaryColorHovered` | color | Same as above, in hovered state. |
+| `secondaryColorPressed` | color | Same as above, in pressed state. |
+| `secondaryColorDisabled` | color | Same as above, in disabled state. |
+| **`secondaryColorForeground`** | color | Text written over elements that already have text color. |
+| `secondaryColorForegroundHovered` | color | Same as above, in hovered state. |
+| `secondaryColorForegroundPressed` | color | Same as above, in pressed state. |
+| `secondaryColorForegroundDisabled` | color | Same as above, in disabled state. |
+| **`secondaryAlternativeColor`** | color | Less important text. |
+| `secondaryAlternativeColorHovered` | color | Same as above, in hovered state. |
+| `secondaryAlternativeColorPressed` | color | Same as above, in pressed state. |
+| `secondaryAlternativeColorDisabled` | color | Same as above, in disabled state. |
+| **`statusColorSuccess`** | color | Feedback for success/validity. |
+| `statusColorSuccessHovered` | color | Same as above, in hovered state. |
+| `statusColorSuccessPressed` | color | Same as above, in pressed state. |
+| `statusColorSuccessDisabled` | color | Same as above, in disabled state. |
+| **`statusColorInfo`** | color | Feedback for information. |
+| `statusColorInfoHovered` | color | Same as above, in hovered state. |
+| `statusColorInfoPressed` | color | Same as above, in pressed state. |
+| `statusColorInfoDisabled` | color | Same as above, in disabled state. |
+| **`statusColorWarning`** | color | Feedback for warning. |
+| `statusColorWarningHovered` | color | Same as above, in hovered state. |
+| `statusColorWarningPressed` | color | Same as above, in pressed state. |
+| `statusColorWarningDisabled` | color | Same as above, in disabled state. |
+| **`statusColorError`** | color | Feedback for error. |
+| `statusColorErrorHovered` | color | Same as above, in hovered state. |
+| `statusColorErrorPressed` | color | Same as above, in pressed state. |
+| `statusColorErrorDisabled` | color | Same as above, in disabled state. |
+| **`statusColorForeground`** | color | Text over status colors. |
+| `statusColorForegroundHovered` | color | Same as above, in hovered state. |
+| `statusColorForegroundPressed` | color | Same as above, in pressed state. |
+| `statusColorForegroundDisabled` | color | Same as above, in disabled state. |
+| **`shadowColor1`** | color | Shadow for elevated elements. |
+| `shadowColor2` | color | Same as above, more contrast. |
+| `shadowColor3` | color | Same as above, more contrast. |
+| **`borderColor`** | color | Borders of textfields, checkboxes, radiobuttons, switches and other elements. |
+| `borderColorHovered` | color | Same as above, in hovered state. |
+| `borderColorPressed` | color | Same as above, in pressed state. |
+| `borderColorDisabled` | color | Same as above, in disabled state. |
+| **`semiTransparentColor1`** | color | To be used over another color to lighten/darken it. |
+| `semiTransparentColor2` | color | Same as above but more contrast. |
+| `semiTransparentColor3` | color | Same as above but more contrast. |
+| `semiTransparentColor4` | color | Same as above but more contrast. |
### Numeric Values
@@ -131,47 +137,47 @@ Example:
}
```
-| Key | Type | Role | Default |
-|:----|:----:|:-----|:-------:|
-| `fontSize` | int | Font size for normal text. | `12` |
-| `fontSizeMonospace` | int | Font size for monospace text. | `13` |
-| `fontSizeH1` | int | Font size for level 1 headers. | `34` |
-| `fontSizeH2` | int | Font size for level 2 headers. | `26` |
-| `fontSizeH3` | int | Font size for level 3 headers. | `22` |
-| `fontSizeH4` | int | Font size for level 4 headers. | `18` |
-| `fontSizeH5` | int | Font size for level 4 headers. | `14` |
-| `fontSizeS1` | int | Font size for level 1 captions. | `10` |
-| `spacing` | int | Spacing between elements. Multiples of this value will be used across the various widgets.| `8` |
-| `iconExtent` | int | Size for icons. Multiple of this value will be used across the various widgets, according to their size. | `16` |
-| `animationDuration` | int | Duration (in milliseconds) for a UI animation, such as a color change. | `192` |
-| `focusAnimationDuration` | int | Duration (in milliseconds) for the focus border animation.Note: can be longer to allow the user to see the focus . | `384` |
-| `sliderAnimationDuration` | int | Duration (in milliseconds) for the slider animation when its value changes.Note: must be quick to feel responsive. | `96` |
-| `borderRadius` | double | Corner radius for most widgets. | `6.0` |
-| `checkBoxBorderRadius` | double | Corner radius for checkboxes.Note: smaller than `borderRadius` because checkboxes are smaller. | `4.0` |
-| `menuItemBorderRadius` | double | Corner radius for menu items.Note: Even smaller than `borderRadius` because menu already have a corner radius and padding. | `4.0` |
-| `menuBarItemBorderRadius` | double | Corner radius for menu bar items. | `2.0` |
-| `borderWidth` | double | Border thickness for widgets that have borders. | `1` |
-| `focusBorderWidth` | int | Border thickness for the focus indicator. | `2` |
-| `controlHeightLarge` | int | Height for most basics `QWidget`s, such as `QPushButton` or `QCheckBox`. | `28` |
-| `controlHeightMedium` | int | Height for a smaller `QWidget`, such as `QSlider`. | `24` |
-| `controlHeightSmall` | int | Height for an even smaller `QWidget`, such as the scroll buttons of a `QMenu`. | `16` |
-| `controlDefaultWidth` | int | Width used as the default width in `sizeHint()` method for widgets that are extensible, such as `QSlider` or `QProgressBar`. | `96` |
-| `dialMarkLength` | int | For a `QDial`, the length of the knob needle that indicates its value. | `8` |
-| `dialMarkThickness` | int | For a `QDial`, the thickness of a tick, if visible. | `2` |
-| `dialTickLength` | int | For a `QDial`, the length of a tick, if visible. | `4` |
-| `dialTickSpacing` | int | For a `QDial`, the spacing between knob and ticks, if visible. | `4` |
-| `dialGrooveThickness` | int | For a `QDial`, the thickness of the knob highlighted zone. | `4` |
-| `sliderTickSize` | int | For a `QSlider`, the length of a tick, if visible. | `3` |
-| `sliderTickSpacing` | int | For a `QSlider`, the spacing between slider and ticks, if visible. | `2` |
-| `sliderTickThickness` | int | For a `QSlider`, the thickness of a tick, if visible.| `1` |
-| `sliderGrooveHeight` | int | For a `QSlider`, the thickness of the groove (i.e. the highlited zone). | `4` |
-| `progressBarGrooveHeight` | int | For a `QProgressBar`, the thickness of the groove (i.e. the highlited zone). | `6` |
-| `scrollBarThicknessFull` | int | For a `QScrollBar`, the thickness when the mouse is over. | `12` |
-| `scrollBarThicknessSmall` | int | For a `QScrollBar`, the thickness when the mouse is not hover. | `6` |
-| `scrollBarMargin` | int | For a `QScrollBar`, the margin between the scrollbar and its parent. | `0` |
-| `tabBarPaddingTop` | int | For a `QTabBar`, the space between the top of the bar and the top of the tabs. | `4` |
-| `tabBarTabMaxWidth` | int | For a `QTabBar`, the maximum width a tab can have.Note: any value below or equal to `0` will be ignored and treated as if there is no maximum width. | `0` |
-| `tabBarTabMinWidth` | int | For a `QTabBar`, the minimum width a tab can have.Note: any value below or equal to `0` will be ignored and treated as if there is no minimum width. | `0` |
+| Key | Type | Role |
+| :------------------------ | :----: | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `fontSize` | int | Font size for normal text (`QLabel`, `QPushButton`, `QCheckBox`, etc.). |
+| `fontSizeMonospace` | int | Font size for monospace text. |
+| `fontSizeH1` | int | Font size for level 1 headers. |
+| `fontSizeH2` | int | Font size for level 2 headers. |
+| `fontSizeH3` | int | Font size for level 3 headers. |
+| `fontSizeH4` | int | Font size for level 4 headers. |
+| `fontSizeH5` | int | Font size for level 4 headers. |
+| `fontSizeS1` | int | Font size for level 1 captions. |
+| `spacing` | int | Spacing between elements. Multiples of this value will be used across the various widgets, including default `QLayout` spacings and margins. |
+| `iconExtent` | int | Size for icons. Multiple of this value will be used across the various widgets, according to their size for `QIcon`. |
+| `animationDuration` | int | Duration (in milliseconds) for a UI animation, such as a color change. |
+| `focusAnimationDuration` | int | Duration (in milliseconds) for the focus border animation.Note: can be longer to allow the user to see the focus . |
+| `sliderAnimationDuration` | int | Duration (in milliseconds) for the slider animation when its value changes.Note: must be quick to feel responsive. |
+| `borderRadius` | double | Corner radius for most widgets. |
+| `checkBoxBorderRadius` | double | Corner radius for checkboxes.Note: smaller than `borderRadius` because checkboxes are smaller. |
+| `menuItemBorderRadius` | double | Corner radius for menu items.Note: Even smaller than `borderRadius` because menu already have a corner radius and padding. |
+| `menuBarItemBorderRadius` | double | Corner radius for menu bar items. |
+| `borderWidth` | double | Border thickness for widgets that have borders. |
+| `focusBorderWidth` | int | Border thickness for the focus indicator. |
+| `controlHeightLarge` | int | Height for most basics `QWidget`s, such as `QPushButton` or `QCheckBox`. |
+| `controlHeightMedium` | int | Height for a smaller `QWidget`, such as `QSlider`. |
+| `controlHeightSmall` | int | Height for an even smaller `QWidget`, such as the scroll buttons of a `QMenu`. |
+| `controlDefaultWidth` | int | Width used as the default width in `sizeHint()` method for widgets that are extensible, such as `QSlider` or `QProgressBar`. |
+| `dialMarkLength` | int | For a `QDial`, the length of the knob needle that indicates its value. |
+| `dialMarkThickness` | int | For a `QDial`, the thickness of a tick, if visible. |
+| `dialTickLength` | int | For a `QDial`, the length of a tick, if visible. |
+| `dialTickSpacing` | int | For a `QDial`, the spacing between knob and ticks, if visible. |
+| `dialGrooveThickness` | int | For a `QDial`, the thickness of the knob highlighted zone. |
+| `sliderTickSize` | int | For a `QSlider`, the length of a tick, if visible. |
+| `sliderTickSpacing` | int | For a `QSlider`, the spacing between slider and ticks, if visible. |
+| `sliderTickThickness` | int | For a `QSlider`, the thickness of a tick, if visible. |
+| `sliderGrooveHeight` | int | For a `QSlider`, the thickness of the groove (i.e. the highlited zone). |
+| `progressBarGrooveHeight` | int | For a `QProgressBar`, the thickness of the groove (i.e. the highlited zone). |
+| `scrollBarThicknessFull` | int | For a `QScrollBar`, the thickness when the mouse is over. |
+| `scrollBarThicknessSmall` | int | For a `QScrollBar`, the thickness when the mouse is not hover. |
+| `scrollBarMargin` | int | For a `QScrollBar`, the margin between the scrollbar and its parent. |
+| `tabBarPaddingTop` | int | For a `QTabBar`, the space between the top of the bar and the top of the tabs. |
+| `tabBarTabMaxWidth` | int | For a `QTabBar`, the maximum width a tab can have.Note: any value below or equal to `0` will be ignored and treated as if there is no maximum width. |
+| `tabBarTabMinWidth` | int | For a `QTabBar`, the minimum width a tab can have.Note: any value below or equal to `0` will be ignored and treated as if there is no minimum width. |
## Full example
@@ -182,55 +188,58 @@ Here is a full Qlementine theme in all its glory. Please note that every value i
"meta": {
"author": "Olivier Cléro",
"name": "Light",
- "version": "1.4.0"
+ "version": "1.5.0"
},
"backgroundColorMain1": "#ffffff",
"backgroundColorMain2": "#f3f3f3",
"backgroundColorMain3": "#e3e3e3",
- "backgroundColorMain4": "#dcdcdc",
+ "backgroundColorMain4": "#dfdfdf",
+
+ "backgroundColorWorkspace": "#b7b7b7",
+ "backgroundColorTabBar": "#dfdfdf",
"borderColor": "#d3d3d3",
+ "borderColorDisabled": "#e9e9e9",
"borderColorHovered": "#b3b3b3",
"borderColorPressed": "#a3a3a3",
- "borderColorDisabled": "#e9e9e9",
"focusColor": "#40a9ff66",
- "neutralColor": "#e1e1e1",
- "neutralColorHovered": "#d9d9d9",
- "neutralColorPressed": "#d2d2d2",
+ "neutralColor": "#d1d1d1",
+ "neutralColorHovered": "#d3d3d3",
+ "neutralColorPressed": "#d5d5d5",
"neutralColorDisabled": "#eeeeee",
- "primaryAlternativeColor": "#106ef9",
- "primaryAlternativeColorHovered": "#107bfd",
- "primaryAlternativeColorPressed": "#108bfd",
- "primaryAlternativeColorDisabled": "#a9d6ff",
-
"primaryColor": "#1890ff",
"primaryColorHovered": "#2c9dff",
- "primaryColorDisabled": "#d1e9ff",
"primaryColorPressed": "#40a9ff",
+ "primaryColorDisabled": "#d1e9ff",
+
+ "primaryAlternativeColor": "#106ef9",
+ "primaryAlternativeColorDisabled": "#a9d6ff",
+ "primaryAlternativeColorHovered": "#107bfd",
+ "primaryAlternativeColorPressed": "#108bfd",
"primaryColorForeground": "#ffffff",
+ "primaryColorForegroundDisabled": "#ecf6ff",
"primaryColorForegroundHovered": "#ffffff",
"primaryColorForegroundPressed": "#ffffff",
- "primaryColorForegroundDisabled": "#ecf6ff",
-
- "secondaryAlternativeColor": "#909090",
- "secondaryAlternativeColorHovered": "#747474",
- "secondaryAlternativeColorPressed": "#828282",
- "secondaryAlternativeColorDisabled": "#c3c3c3",
"secondaryColor": "#404040",
"secondaryColorHovered": "#333333",
"secondaryColorPressed": "#262626",
"secondaryColorDisabled": "#d4d4d4",
+ "secondaryAlternativeColor": "#909090",
+ "secondaryAlternativeColorDisabled": "#c3c3c3",
+ "secondaryAlternativeColorHovered": "#747474",
+ "secondaryAlternativeColorPressed": "#828282",
+
"secondaryColorForeground": "#ffffff",
+ "secondaryColorForegroundDisabled": "#ededed",
"secondaryColorForegroundHovered": "#ffffff",
"secondaryColorForegroundPressed": "#ffffff",
- "secondaryColorForegroundDisabled": "#ededed",
"semiTransparentColor1": "#0000000a",
"semiTransparentColor2": "#00000019",
@@ -241,16 +250,16 @@ Here is a full Qlementine theme in all its glory. Please note that every value i
"shadowColor2": "#00000040",
"shadowColor3": "#00000060",
- "statusColorForeground": "#ffffff",
- "statusColorForegroundDisabled": "#ffffff99",
- "statusColorForegroundHovered": "#ffffff",
- "statusColorForegroundPressed": "#ffffff",
-
"statusColorError": "#e96b72",
"statusColorErrorHovered": "#f47c83",
"statusColorErrorPressed": "#ff9197",
"statusColorErrorDisabled": "#f9dadc",
+ "statusColorForeground": "#ffffff",
+ "statusColorForegroundHovered": "#ffffff",
+ "statusColorForegroundPressed": "#ffffff",
+ "statusColorForegroundDisabled": "#ffffff99",
+
"statusColorInfo": "#1ba8d5",
"statusColorInfoHovered": "#1eb5e5",
"statusColorInfoPressed": "#29c0f0",
diff --git a/docs/usage.md b/docs/usage.md
index 07385f4..bdb26ed 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -26,6 +26,26 @@ Define the `QStyle` on your `QApplication`.
QApplication app(argc, argv);
auto* style = new oclero::qlementine::QlementineStyle(&app);
-style->setThemeJsonPath(":/light.json");
QApplication::setStyle(style);
```
+
+## Themes
+
+You may want to use your own JSON theme.
+
+```c++
+style->setThemeJsonPath(":/path/to/your/theme.json");
+```
+
+Additionnally, you can also use `ThemeManager` to handle that for you.
+
+```c++
+// Link a ThemeManager to a QlementineStyle.
+auto* themeManager = new oclero::qlementine::ThemeManager(style);
+
+// Load the directory where you store your own JSON themes.
+themeManager->loadDirectory(":/themes");
+
+// Define theme on QStyle.
+themeManager->setCurrentTheme("Light");
+```
diff --git a/docs/widgets.md b/docs/widgets.md
index ecea3ce..9a63127 100644
--- a/docs/widgets.md
+++ b/docs/widgets.md
@@ -6,7 +6,7 @@ More information about them coming soon. You can check them in the Sandbox appli
## AbstractItemListWidget
-Base class for [`NavigationBar`](#navigation-bar) and [`SegmentedControl`](#segmented-control).
+Base class for [`NavigationBar`](#navigationbar) and [`SegmentedControl`](#segmentedcontrol).
## Action
@@ -80,13 +80,13 @@ Improves `QFocusFrame` by adding a corner radius property.
A widget that allows to switch between a range of elements, such as seen on iOS or macOS.
-![SegmentedControl](assets/images/widgets/segmentedcontrol)
+![SegmentedControl](assets/images/widgets/segmentedcontrol.png)
## StatusBadgeWidget
Widget to display a status icon: info, warning, error, success. Available in two standard sizes.
-![StatusBadgeWidget](assets/images/widgets/badges)
+![StatusBadgeWidget](assets/images/widgets/badges.png)
## Switch
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 496ba05..05ee9a6 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -20,7 +20,10 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/BadgeUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/ColorUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FontUtils.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/IconUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/ImageUtils.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/LayoutUtils.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/MenuUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/PrimitiveUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/RadiusesF.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StateUtils.cpp
@@ -69,7 +72,10 @@ set(HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/BadgeUtils.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/ColorUtils.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/FontUtils.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/IconUtils.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/ImageUtils.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/LayoutUtils.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/MenuUtils.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/PrimitiveUtils.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/RadiusesF.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/oclero/qlementine/utils/StateUtils.hpp
@@ -120,34 +126,37 @@ target_include_directories(${LIB_TARGET_NAME}
$
)
-target_link_libraries(${LIB_TARGET_NAME} PUBLIC
- Qt6::Core
- Qt6::Widgets
- Qt6::Svg
+target_link_libraries(${LIB_TARGET_NAME}
+ PUBLIC
+ Qt::Core
+ Qt::Widgets
+ Qt::Svg
)
set_target_properties(${LIB_TARGET_NAME}
PROPERTIES
- OUTPUT_NAME ${LIB_TARGET_NAME}
- PROJECT_LABEL ${LIB_TARGET_NAME}
- FOLDER lib
- SOVERSION ${PROJECT_VERSION_MAJOR}
- VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
- DEBUG_POSTFIX _debug
+ OUTPUT_NAME ${LIB_TARGET_NAME}
+ PROJECT_LABEL ${LIB_TARGET_NAME}
+ FOLDER lib
+ SOVERSION ${PROJECT_VERSION_MAJOR}
+ VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
+ DEBUG_POSTFIX _debug
CMAKE_AUTORCC ON
CMAKE_AUTOMOC ON
CMAKE_AUTOUIC ON
)
-target_compile_options(${LIB_TARGET_NAME} PRIVATE
- $<$:/MP /WX /W4>
- $<$>:-Wall -Wextra -Werror>
+target_compile_options(${LIB_TARGET_NAME}
+ PRIVATE
+ $<$:/MP /WX /W4>
+ $<$>:-Wall -Wextra -Werror>
)
# Create source groups.
-source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES
- ${HEADERS}
- ${SOURCES}
+source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}
+ FILES
+ ${HEADERS}
+ ${SOURCES}
)
# Select correct startup project in Visual Studio.
diff --git a/lib/include/oclero/qlementine/style/QlementineStyle.hpp b/lib/include/oclero/qlementine/style/QlementineStyle.hpp
index 46bf425..45d1256 100644
--- a/lib/include/oclero/qlementine/style/QlementineStyle.hpp
+++ b/lib/include/oclero/qlementine/style/QlementineStyle.hpp
@@ -5,6 +5,7 @@
#include
#include
+#include
#include
@@ -20,8 +21,6 @@ class QlementineStyle : public QCommonStyle {
Q_OBJECT
Q_PROPERTY(bool animationsEnabled READ animationsEnabled WRITE setAnimationsEnabled NOTIFY animationsEnabledChanged)
- Q_PROPERTY(bool useMenuForComboBoxPopup READ useMenuForComboBoxPopup WRITE setUseMenuForComboBoxPopup NOTIFY
- useMenuForComboBoxPopupChanged)
public:
enum class StandardPixmapExt {
@@ -60,10 +59,6 @@ class QlementineStyle : public QCommonStyle {
void setAnimationsEnabled(bool enabled);
Q_SIGNAL void animationsEnabledChanged();
- bool useMenuForComboBoxPopup() const;
- void setUseMenuForComboBoxPopup(bool useMenu);
- Q_SIGNAL void useMenuForComboBoxPopupChanged();
-
void triggerCompleteRepaint();
void setAutoIconColor(AutoIconColor autoIconColor);
@@ -74,7 +69,15 @@ class QlementineStyle : public QCommonStyle {
QPixmap getColorizedPixmap(
const QPixmap& input, AutoIconColor autoIconColor, const QColor& fgcolor, const QColor& textColor) const;
- static QIcon makeIcon(const QString& svgPath);
+
+ QIcon makeThemedIcon(
+ const QString& svgPath, const QSize& size = QSize(16, 16), ColorRole role = ColorRole::Secondary) const;
+
+ QIcon makeThemedIconFromName(
+ const QString& name, const QSize& size = QSize(16, 16), ColorRole role = ColorRole::Secondary) const;
+
+ // Allows to customize quickly the way QlementineStyle gets its icons. SVG paths preferred.
+ void setIconPathGetter(const std::function& func);
public: // QStyle overrides.
void drawPrimitive(
@@ -204,7 +207,7 @@ class QlementineStyle : public QCommonStyle {
virtual QColor const& menuBarItemBackgroundColor(MouseState const mouse, SelectionState const selected) const;
virtual QColor const& menuBarItemForegroundColor(MouseState const mouse, SelectionState const selected) const;
- virtual QColor const& tabBarBackgroundColor() const;
+ virtual QColor const& tabBarBackgroundColor(MouseState const mouse) const;
virtual QColor const& tabBarShadowColor() const;
virtual QColor const& tabBarBottomShadowColor() const;
virtual QColor const& tabBackgroundColor(MouseState const mouse, SelectionState const selected) const;
@@ -237,6 +240,8 @@ class QlementineStyle : public QCommonStyle {
virtual QColor const& labelForegroundColor(MouseState const mouse, const QWidget* w = nullptr) const;
virtual QColor const& labelCaptionForegroundColor(MouseState const mouse) const;
+ virtual QColor const& iconForegroundColor(MouseState const mouse, ColorRole const role) const;
+
virtual QColor const& toolBarBackgroundColor() const;
virtual QColor const& toolBarBorderColor() const;
virtual QColor const& toolBarSeparatorColor() const;
@@ -251,7 +256,7 @@ class QlementineStyle : public QCommonStyle {
virtual QColor const& groupBoxTitleColor(MouseState const mouse, const QWidget* w = nullptr) const;
virtual QColor const& groupBoxBorderColor(MouseState const mouse) const;
- virtual QColor const& groupBoxBackgroundColor(MouseState const mouse) const;
+ virtual QColor groupBoxBackgroundColor(MouseState const mouse) const;
virtual QColor const& statusColor(Status const status, MouseState const mouse) const;
virtual QColor focusBorderColor(Status status) const;
@@ -281,7 +286,11 @@ class QlementineStyle : public QCommonStyle {
virtual QColor const& statusBarBorderColor() const;
virtual QColor const& statusBarSeparatorColor() const;
+ virtual QColor const& splitterColor(MouseState const mouse) const;
+
private:
std::unique_ptr _impl;
};
+
+QlementineStyle* appStyle();
} // namespace oclero::qlementine
diff --git a/lib/include/oclero/qlementine/style/Theme.hpp b/lib/include/oclero/qlementine/style/Theme.hpp
index 069a65c..32c3a6a 100644
--- a/lib/include/oclero/qlementine/style/Theme.hpp
+++ b/lib/include/oclero/qlementine/style/Theme.hpp
@@ -3,6 +3,8 @@
#pragma once
+#include
+
#include
#include
@@ -32,8 +34,9 @@ struct ThemeMeta {
class Theme {
public: // Ctor.
Theme();
- Theme(QString const& jsonPath);
- explicit Theme(QJsonDocument const& jsonDoc);
+
+ static std::optional fromJsonPath(const QString& jsonPath);
+ static std::optional fromJsonDoc(const QJsonDocument& jsonDoc);
Theme(Theme const& other) = default;
Theme(Theme&& other) = default;
@@ -48,89 +51,92 @@ class Theme {
public: // Values.
ThemeMeta meta;
- QColor backgroundColorMain1{ 0xFFFFFF };
- QColor backgroundColorMain2{ 0xF3F3F3 };
- QColor backgroundColorMain3{ 0xE3E3E3 };
- QColor backgroundColorMain4{ 0xDCDCDC };
- QColor backgroundColorMainTransparent{ QRgba64::fromArgb32(0x00FAFAFA) };
+ QColor backgroundColorMain1{ 0xffffff };
+ QColor backgroundColorMain2{ 0xf3f3f3 };
+ QColor backgroundColorMain3{ 0xe3e3e3 };
+ QColor backgroundColorMain4{ 0xdcdcdc };
+ QColor backgroundColorMainTransparent{ QRgba64::fromArgb32(0x00fafafa) };
+
+ QColor backgroundColorWorkspace{ 0xb7b7b7 };
+ QColor backgroundColorTabBar{ 0xb7b7b7 };
- QColor neutralColor{ 0xE1E1E1 };
- QColor neutralColorHovered{ 0xDADADA };
- QColor neutralColorPressed{ 0xD2D2D2 };
- QColor neutralColorDisabled{ 0xEEEEEE };
+ QColor neutralColor{ 0xe1e1e1 };
+ QColor neutralColorHovered{ 0xdadada };
+ QColor neutralColorPressed{ 0xd2d2d2 };
+ QColor neutralColorDisabled{ 0xeeeeee };
QColor neutralColorTransparent{ QRgba64::fromArgb32(0x00E1E1E1) };
QColor focusColor{ QRgba64::fromArgb32(0x6640a9ff) };
- QColor primaryColor{ 0x1890FF };
- QColor primaryColorHovered{ 0x2C9DFF };
- QColor primaryColorPressed{ 0x40A9FF };
- QColor primaryColorDisabled{ 0xD1E9FF };
+ QColor primaryColor{ 0x1890ff };
+ QColor primaryColorHovered{ 0x2c9dff };
+ QColor primaryColorPressed{ 0x40a9ff };
+ QColor primaryColorDisabled{ 0xd1e9ff };
QColor primaryColorTransparent{ QRgba64::fromArgb32(0x001890FF) };
- QColor primaryColorForeground{ 0xFFFFFF };
- QColor primaryColorForegroundHovered{ 0xFFFFFF };
- QColor primaryColorForegroundPressed{ 0xFFFFFF };
- QColor primaryColorForegroundDisabled{ 0xECF6FF };
- QColor primaryColorForegroundTransparent{ QRgba64::fromArgb32(0x00FFFFFF) };
+ QColor primaryColorForeground{ 0xffffff };
+ QColor primaryColorForegroundHovered{ 0xffffff };
+ QColor primaryColorForegroundPressed{ 0xffffff };
+ QColor primaryColorForegroundDisabled{ 0xecf6ff };
+ QColor primaryColorForegroundTransparent{ QRgba64::fromArgb32(0x00ffffff) };
- QColor primaryAlternativeColor{ 0x106EF9 };
- QColor primaryAlternativeColorHovered{ 0x0F7BFD };
- QColor primaryAlternativeColorPressed{ 0x0F8BFD };
+ QColor primaryAlternativeColor{ 0x106ef9 };
+ QColor primaryAlternativeColorHovered{ 0x107bfd };
+ QColor primaryAlternativeColorPressed{ 0x108bfd };
QColor primaryAlternativeColorDisabled{ 0xa9d6ff };
QColor primaryAlternativeColorTransparent{ QRgba64::fromArgb32(0x001875ff) };
QColor secondaryColor{ 0x404040 };
QColor secondaryColorHovered{ 0x333333 };
QColor secondaryColorPressed{ 0x262626 };
- QColor secondaryColorDisabled{ 0xD4D4D4 };
+ QColor secondaryColorDisabled{ 0xd4d4d4 };
QColor secondaryColorTransparent{ QRgba64::fromArgb32(0x00404040) };
- QColor secondaryColorForeground{ 0xFFFFFF };
- QColor secondaryColorForegroundHovered{ 0xFFFFFF };
- QColor secondaryColorForegroundPressed{ 0xFFFFFF };
- QColor secondaryColorForegroundDisabled{ 0xEDEDED };
- QColor secondaryColorForegroundTransparent{ QRgba64::fromArgb32(0x00FFFFFF) };
+ QColor secondaryColorForeground{ 0xffffff };
+ QColor secondaryColorForegroundHovered{ 0xffffff };
+ QColor secondaryColorForegroundPressed{ 0xffffff };
+ QColor secondaryColorForegroundDisabled{ 0xededed };
+ QColor secondaryColorForegroundTransparent{ QRgba64::fromArgb32(0x00ffffff) };
QColor secondaryAlternativeColor{ 0x909090 };
QColor secondaryAlternativeColorHovered{ 0x747474 };
QColor secondaryAlternativeColorPressed{ 0x828282 };
- QColor secondaryAlternativeColorDisabled{ 0xC3C3C3 };
+ QColor secondaryAlternativeColorDisabled{ 0xc3c3c3 };
QColor secondaryAlternativeColorTransparent{ QRgba64::fromArgb32(0x00909090) };
- QColor statusColorSuccess{ 0x2BB5A0 };
- QColor statusColorSuccessHovered{ 0x3CBFAB };
- QColor statusColorSuccessPressed{ 0x4ECDB9 };
- QColor statusColorSuccessDisabled{ 0xD5F0EC };
- QColor statusColorInfo{ 0x1BA8D5 };
- QColor statusColorInfoHovered{ 0x1EB5E5 };
+ QColor statusColorSuccess{ 0x2bb5a0 };
+ QColor statusColorSuccessHovered{ 0x3cbfab };
+ QColor statusColorSuccessPressed{ 0x4ecdb9 };
+ QColor statusColorSuccessDisabled{ 0xd5f0ec };
+ QColor statusColorInfo{ 0x1ba8d5 };
+ QColor statusColorInfoHovered{ 0x1eb5e5 };
QColor statusColorInfoPressed{ 0x29c0f0 };
- QColor statusColorInfoDisabled{ 0xC7EAF5 };
+ QColor statusColorInfoDisabled{ 0xc7eaf5 };
QColor statusColorWarning{ 0xfbc064 };
- QColor statusColorWarningHovered{ 0xFFCF6C };
- QColor statusColorWarningPressed{ 0xFFD880 };
- QColor statusColorWarningDisabled{ 0xFEEFD8 };
- QColor statusColorError{ 0xE96B72 };
- QColor statusColorErrorHovered{ 0xF47C83 };
- QColor statusColorErrorPressed{ 0xFF9197 };
- QColor statusColorErrorDisabled{ 0xF9DADC };
- QColor statusColorForeground{ 0xFFFFFF };
- QColor statusColorForegroundHovered{ 0xFFFFFF };
- QColor statusColorForegroundPressed{ 0xFFFFFF };
- QColor statusColorForegroundDisabled{ QRgba64::fromArgb32(0x99FFFFFF) };
+ QColor statusColorWarningHovered{ 0xffcf6c };
+ QColor statusColorWarningPressed{ 0xffd880 };
+ QColor statusColorWarningDisabled{ 0xfeefd8 };
+ QColor statusColorError{ 0xe96b72 };
+ QColor statusColorErrorHovered{ 0xf47c83 };
+ QColor statusColorErrorPressed{ 0xff9197 };
+ QColor statusColorErrorDisabled{ 0xf9dadc };
+ QColor statusColorForeground{ 0xffffff };
+ QColor statusColorForegroundHovered{ 0xffffff };
+ QColor statusColorForegroundPressed{ 0xffffff };
+ QColor statusColorForegroundDisabled{ QRgba64::fromArgb32(0x99ffffff) };
QColor shadowColor1{ QRgba64::fromArgb32(0x20000000) };
QColor shadowColor2{ QRgba64::fromArgb32(0x40000000) };
QColor shadowColor3{ QRgba64::fromArgb32(0x60000000) };
QColor shadowColorTransparent{ QRgba64::fromArgb32(0x00000000) };
- QColor borderColor{ 0xD3D3D3 };
- QColor borderColorHovered{ 0xB3B3B3 };
- QColor borderColorPressed{ 0xA3A3A3 };
- QColor borderColorDisabled{ 0xE9E9E9 };
- QColor borderColorTransparent{ QRgba64::fromArgb32(0x00D3D3D3) };
+ QColor borderColor{ 0xd3d3d3 };
+ QColor borderColorHovered{ 0xb3b3b3 };
+ QColor borderColorPressed{ 0xa3a3a3 };
+ QColor borderColorDisabled{ 0xe9e9e9 };
+ QColor borderColorTransparent{ QRgba64::fromArgb32(0x00d3d3d3) };
- QColor semiTransparentColor1{ QRgba64::fromArgb32(0x0A000000) };
+ QColor semiTransparentColor1{ QRgba64::fromArgb32(0x0000000) };
QColor semiTransparentColor2{ QRgba64::fromArgb32(0x19000000) };
QColor semiTransparentColor3{ QRgba64::fromArgb32(0x21000000) };
QColor semiTransparentColor4{ QRgba64::fromArgb32(0x28000000) };
@@ -198,6 +204,6 @@ class Theme {
private:
void initializeFonts();
void initializePalette();
- void initializeFromJson(QJsonDocument const& jsonDoc);
+ bool initializeFromJson(QJsonDocument const& jsonDoc);
};
} // namespace oclero::qlementine
diff --git a/lib/include/oclero/qlementine/style/ThemeManager.hpp b/lib/include/oclero/qlementine/style/ThemeManager.hpp
index 5db2d9e..ba66528 100644
--- a/lib/include/oclero/qlementine/style/ThemeManager.hpp
+++ b/lib/include/oclero/qlementine/style/ThemeManager.hpp
@@ -29,6 +29,8 @@ class ThemeManager : public QObject {
const std::vector& themes() const;
void addTheme(const Theme& theme);
+ void loadDirectory(const QString& path);
+
QString currentTheme() const;
void setCurrentTheme(const QString& key);
Q_SIGNAL void currentThemeChanged();
@@ -44,8 +46,6 @@ class ThemeManager : public QObject {
int currentThemeIndex() const;
void setCurrentThemeIndex(int index);
- QString getLocalizedThemeName(const QString& baseThemeName) const;
-
private:
void synchronizeThemeOnStyle();
diff --git a/lib/include/oclero/qlementine/utils/ColorUtils.hpp b/lib/include/oclero/qlementine/utils/ColorUtils.hpp
index 248dab1..3bc51ca 100644
--- a/lib/include/oclero/qlementine/utils/ColorUtils.hpp
+++ b/lib/include/oclero/qlementine/utils/ColorUtils.hpp
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Olivier Cléro
// SPDX-License-Identifier: MIT
+#pragma once
+
#include
#include
#include
diff --git a/lib/include/oclero/qlementine/utils/FontUtils.hpp b/lib/include/oclero/qlementine/utils/FontUtils.hpp
index 95d5849..77090ba 100644
--- a/lib/include/oclero/qlementine/utils/FontUtils.hpp
+++ b/lib/include/oclero/qlementine/utils/FontUtils.hpp
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Olivier Cléro
// SPDX-License-Identifier: MIT
+#pragma once
+
#include
#include
diff --git a/lib/include/oclero/qlementine/utils/IconUtils.hpp b/lib/include/oclero/qlementine/utils/IconUtils.hpp
new file mode 100644
index 0000000..bdc6e16
--- /dev/null
+++ b/lib/include/oclero/qlementine/utils/IconUtils.hpp
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Olivier Cléro
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include
+#include
+
+namespace oclero::qlementine {
+struct IconTheme {
+ QColor normal;
+ QColor disabled;
+ QColor checkedNormal;
+ QColor checkedDisabled;
+
+ IconTheme(const QColor& normal);
+ IconTheme(const QColor& normal, const QColor& disabled);
+ IconTheme(const QColor& normal, const QColor& disabled, const QColor& checkedNormal, QColor checkedDisabled);
+
+ const QColor& color(QIcon::Mode mode, QIcon::State state) const;
+};
+
+/// Makes an icon from the file located at the path in parameter. Fixes the standard Qt behavior.
+[[maybe_unused]] QIcon makeIconFromSvg(const QString& svgPath, const QSize& size);
+
+/// Makes an icon from the file located at the path in parameter and colorizes the QPixmaps. Fixes the standard Qt behavior.
+[[maybe_unused]] QIcon makeIconFromSvg(
+ const QString& svgPath, const IconTheme& iconTheme, const QSize& size = QSize(16, 16));
+} // namespace oclero::qlementine
diff --git a/lib/include/oclero/qlementine/utils/ImageUtils.hpp b/lib/include/oclero/qlementine/utils/ImageUtils.hpp
index ad31628..b0e6261 100644
--- a/lib/include/oclero/qlementine/utils/ImageUtils.hpp
+++ b/lib/include/oclero/qlementine/utils/ImageUtils.hpp
@@ -82,9 +82,6 @@ enum class AutoIconColor {
/// Gets the pixmap in the cache, or creates it if not yet there.
QPixmap getCachedPixmap(QPixmap const& input, QColor const& color, ColorizeMode mode);
-/// Makes an icon from the file located at the path in parameter. Fixes the standard Qt behavior.
-QIcon makeIconFromSvg(const QString& svgPath, const QSize& size);
-
/// Makes a QPixmap from the file located at the path in parameter at the desired size.
QPixmap makePixmapFromSvg(const QString& svgPath, const QSize& size);
diff --git a/lib/include/oclero/qlementine/utils/LayoutUtils.hpp b/lib/include/oclero/qlementine/utils/LayoutUtils.hpp
new file mode 100644
index 0000000..fe4365b
--- /dev/null
+++ b/lib/include/oclero/qlementine/utils/LayoutUtils.hpp
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: Olivier Cléro
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include
+#include
+
+#include
+
+class QWidget;
+
+namespace oclero::qlementine {
+/// Retrieves the widget's QStyle margins.
+QMargins getLayoutMargins(const QWidget* widget);
+
+/// Retrieves the widget's QStyle horizontal spacing.
+int getLayoutHSpacing(const QWidget* widget);
+
+/// Retrieves the widget's QStyle vertical spacing.
+int getLayoutVSpacing(const QWidget* widget);
+
+/// Retrieves the widget's QStyle horizontal spacing and margins.
+std::tuple getHLayoutProps(const QWidget* widget);
+
+/// Retrieves the widget's QStyle vertical spacing and margins.
+std::tuple getVLayoutProps(const QWidget* widget);
+
+/// Retrieves the widget's QStyle vertical/horizontal spacings and margins.
+std::tuple getFormLayoutProps(const QWidget* widget);
+
+/// Remove and deletes all the elements in the layout.
+void clearLayout(QLayout* layout);
+} // namespace oclero::qlementine
diff --git a/lib/include/oclero/qlementine/utils/MenuUtils.hpp b/lib/include/oclero/qlementine/utils/MenuUtils.hpp
new file mode 100644
index 0000000..95fd51e
--- /dev/null
+++ b/lib/include/oclero/qlementine/utils/MenuUtils.hpp
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: Olivier Cléro
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include
+
+class QMenu;
+class QAction;
+
+namespace oclero::qlementine {
+QMenu* getTopLevelMenu(QMenu* menu);
+
+void flashAction(QAction* action, QMenu* menu, const std::function& onAnimationFinished);
+} // namespace oclero::qlementine
diff --git a/lib/include/oclero/qlementine/utils/PrimitiveUtils.hpp b/lib/include/oclero/qlementine/utils/PrimitiveUtils.hpp
index e715a0f..805716d 100644
--- a/lib/include/oclero/qlementine/utils/PrimitiveUtils.hpp
+++ b/lib/include/oclero/qlementine/utils/PrimitiveUtils.hpp
@@ -14,7 +14,7 @@
#include
namespace oclero::qlementine {
-static constexpr auto QLEMENTINE_PI = 3.14159265358979323846;
+[[maybe_unused]] static constexpr auto QLEMENTINE_PI = 3.14159265358979323846;
/// Gets the device pixel ratio for the QWidget.
double getPixelRatio(QWidget const* w);
diff --git a/lib/include/oclero/qlementine/utils/WidgetUtils.hpp b/lib/include/oclero/qlementine/utils/WidgetUtils.hpp
index e8d33cc..ae579b9 100644
--- a/lib/include/oclero/qlementine/utils/WidgetUtils.hpp
+++ b/lib/include/oclero/qlementine/utils/WidgetUtils.hpp
@@ -13,14 +13,10 @@ QWidget* makeHorizontalLine(QWidget* parentWidget, int maxWidth = -1);
void centerWidget(QWidget* widget, QWidget* host = nullptr);
-QMargins getDefaultMargins(const QStyle* style);
-
qreal getDpi(const QWidget* widget);
QWindow* getWindow(const QWidget* widget);
-void clearLayout(QLayout* layout);
-
template
T* findFirstParentOfType(QWidget* child) {
auto* parent = child;
diff --git a/lib/include/oclero/qlementine/widgets/Expander.hpp b/lib/include/oclero/qlementine/widgets/Expander.hpp
index 1722801..4d061e9 100644
--- a/lib/include/oclero/qlementine/widgets/Expander.hpp
+++ b/lib/include/oclero/qlementine/widgets/Expander.hpp
@@ -8,11 +8,13 @@
#include
namespace oclero::qlementine {
-/// A QWidget that allows to expand vertically, displaying its content.
+/// A QWidget that allows to expand vertically or horizontally,
+/// revealing or hiding its content with an animation.
class Expander : public QWidget {
Q_OBJECT
Q_PROPERTY(bool expanded READ expanded WRITE setExpanded NOTIFY expandedChanged)
+ Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
public:
Expander(QWidget* parent = nullptr);
@@ -20,6 +22,16 @@ class Expander : public QWidget {
bool expanded() const;
Q_SLOT void setExpanded(bool expanded);
Q_SIGNAL void expandedChanged();
+ void toggleExpanded();
+
+ Q_SIGNAL void aboutToExpand();
+ Q_SIGNAL void aboutToShrink();
+ Q_SIGNAL void didExpand();
+ Q_SIGNAL void didShrink();
+
+ Qt::Orientation orientation() const;
+ Q_SLOT void setOrientation(Qt::Orientation orientation);
+ Q_SIGNAL void orientationChanged();
QWidget* content() const;
void setContent(QWidget* content);
@@ -37,6 +49,7 @@ class Expander : public QWidget {
private:
bool _expanded{ false };
+ Qt::Orientation _orientation{ Qt::Orientation::Vertical };
QVariantAnimation _animation;
QPointer _content{ nullptr };
};
diff --git a/lib/src/style/Delegates.cpp b/lib/src/style/Delegates.cpp
index 3c3aa3f..863976d 100644
--- a/lib/src/style/Delegates.cpp
+++ b/lib/src/style/Delegates.cpp
@@ -21,8 +21,11 @@ void ComboBoxDelegate::paint(QPainter* p, const QStyleOptionViewItem& opt, const
const auto& theme = _qlementineStyle ? _qlementineStyle->theme() : Theme{};
const auto isSeparator = idx.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator");
+ const auto contentMargin = _qlementineStyle->pixelMetric(QStyle::PM_MenuHMargin);
+ const auto contentRect = opt.rect.marginsRemoved({ contentMargin, 0, contentMargin, 0 });
+
if (isSeparator) {
- const auto& rect = opt.rect;
+ const auto& rect = contentRect;
const auto& color =
_qlementineStyle ? _qlementineStyle->toolBarSeparatorColor() : Theme().secondaryAlternativeColorDisabled;
const auto lineW = theme.borderWidth;
@@ -37,11 +40,11 @@ void ComboBoxDelegate::paint(QPainter* p, const QStyleOptionViewItem& opt, const
const auto mouse = getMenuItemMouseState(opt.state);
// Background.
+ const auto& bgRect = contentRect;
const auto hPadding = theme.spacing;
- const auto& bgRect = opt.rect;
const auto& bgColor =
_qlementineStyle ? _qlementineStyle->menuItemBackgroundColor(mouse) : Theme().primaryColorTransparent;
- constexpr auto radius = 0;
+ const auto radius = _qlementineStyle->theme().borderRadius - contentMargin / 2;
p->setRenderHint(QPainter::Antialiasing, true);
p->setPen(Qt::NoPen);
p->setBrush(bgColor);
@@ -128,8 +131,9 @@ QSize ComboBoxDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelIn
const auto h = theme.spacing + theme.borderWidth;
return QSize{ h, h };
} else {
+ const auto contentMargin = _qlementineStyle->pixelMetric(QStyle::PM_MenuHMargin);
const auto hPadding = theme.spacing;
- const auto vPadding = theme.spacing / 2;
+ const auto vPadding = theme.spacing;
const auto iconSize = theme.iconSize;
const auto spacing = theme.spacing;
const auto& fm = opt.fontMetrics;
@@ -142,7 +146,7 @@ QSize ComboBoxDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelIn
iconVariant.isValid() && iconVariant.userType() == QMetaType::QIcon ? iconVariant.value() : QIcon{};
const auto textW = qlementine::textWidth(fm, text);
const auto iconW = !icon.isNull() ? iconSize.width() + spacing : 0;
- const auto w = std::max(0, hPadding + iconW + textW + hPadding);
+ const auto w = std::max(0, contentMargin * 2 + hPadding + iconW + textW + hPadding);
const auto h = std::max(theme.controlHeightMedium, std::max(iconSize.height(), vPadding) + vPadding);
return QSize{ w, h };
}
diff --git a/lib/src/style/EventFilters.cpp b/lib/src/style/EventFilters.cpp
index deb0859..e904bc3 100644
--- a/lib/src/style/EventFilters.cpp
+++ b/lib/src/style/EventFilters.cpp
@@ -6,6 +6,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -16,10 +18,15 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
namespace oclero::qlementine {
LineEditButtonEventFilter::LineEditButtonEventFilter(
- QlementineStyle& style, WidgetAnimationManager& animManager, QToolButton* button)
+ QlementineStyle* style, WidgetAnimationManager& animManager, QToolButton* button)
: QObject(button)
, _style(style)
, _animManager(animManager)
@@ -44,7 +51,7 @@ bool LineEditButtonEventFilter::eventFilter(QObject* watchedObject, QEvent* evt)
// Instead, place the button by ourselves.
const auto* parentLineEdit = _button->parentWidget();
const auto parentRect = parentLineEdit->rect();
- const auto& theme = _style.theme();
+ const auto& theme = _style ? _style->theme() : Theme{};
const auto buttonH = theme.controlHeightMedium;
const auto buttonW = buttonH;
const auto spacing = theme.spacing / 2;
@@ -63,29 +70,46 @@ bool LineEditButtonEventFilter::eventFilter(QObject* watchedObject, QEvent* evt)
const auto hovered = _button->underMouse();
const auto pressed = _button->isDown();
const auto mouse = getMouseState(pressed, hovered, enabled);
- const auto& theme = _style.theme();
+ const auto& theme = _style ? _style->theme() : Theme{};
const auto rect = _button->rect();
- const auto& bgColor = _style.toolButtonBackgroundColor(mouse, ColorRole::Secondary);
+
+ const auto& bgColor =
+ _style ? _style->toolButtonBackgroundColor(mouse, ColorRole::Secondary)
+ : _button->style()->standardPalette().color(getPaletteColorGroup(mouse), QPalette::ColorRole::ButtonText);
+ const auto& fgColor =
+ _style ? _style->toolButtonForegroundColor(mouse, ColorRole::Secondary)
+ : _button->style()->standardPalette().color(getPaletteColorGroup(mouse), QPalette::ColorRole::Button);
+ const auto animationDuration = _style ? _style->theme().animationDuration : 0;
+ const auto& currentBgColor = _animManager.animateBackgroundColor(_button, bgColor, animationDuration);
+ const auto& currentFgColor = _animManager.animateForegroundColor(_button, fgColor, animationDuration);
+
+ // Get opacity animated in qlinedit_p.cpp:436
+ const auto opacity = _button->property(QByteArrayLiteral("opacity")).toDouble();
+
const auto circleH = theme.controlHeightMedium;
const auto circleW = circleH;
const auto circleX = rect.x() + (rect.width() - circleW) / 2;
const auto circleY = rect.y() + (rect.height() - circleH) / 2;
const auto circleRect = QRect(QPoint{ circleX, circleY }, QSize{ circleW, circleH });
- // Get opacity animated in qlinedit_p.cpp:436
- const auto opacity = _button->property(QByteArrayLiteral("opacity")).toDouble();
+
const auto pixmap = getPixmap(_button->icon(), theme.iconSize, mouse, CheckState::NotChecked, _button);
+ const auto autoIconColor = _style ? _style->autoIconColor(_button) : AutoIconColor::None;
+ const auto& colorizedPixmap = _style->getColorizedPixmap(pixmap, autoIconColor, currentFgColor, currentFgColor);
const auto pixmapX = circleRect.x() + (circleRect.width() - theme.iconSize.width()) / 2;
const auto pixmapY = circleRect.y() + (circleRect.height() - theme.iconSize.height()) / 2;
const auto pixmapRect = QRect{ { pixmapX, pixmapY }, theme.iconSize };
- const auto& currentBgColor = _animManager.animateBackgroundColor(_button, bgColor, theme.animationDuration);
QPainter p(_button);
p.setOpacity(opacity);
p.setPen(Qt::NoPen);
- p.setBrush(currentBgColor);
p.setRenderHint(QPainter::Antialiasing, true);
+
+ // Background.
+ p.setBrush(currentBgColor);
p.drawEllipse(circleRect);
- p.drawPixmap(pixmapRect, pixmap);
+
+ // Foreground.
+ p.drawPixmap(pixmapRect, colorizedPixmap);
evt->accept();
return true;
@@ -231,12 +255,20 @@ bool TabBarEventFilter::eventFilter(QObject* watchedObject, QEvent* evt) {
_tabBar->setIconSize(_tabBar->iconSize());
} else if (type == QEvent::Wheel) {
const auto* wheelEvent = static_cast(evt);
+
+ // Block non-horizontal scorll.
+ const bool wheelVertical = qAbs(wheelEvent->angleDelta().y()) > qAbs(wheelEvent->angleDelta().x());
+ if (wheelVertical) {
+ evt->ignore();
+ return true;
+ }
+
auto delta = wheelEvent->pixelDelta().x();
// If delta is null, it might be because we are on MacOS, using a trackpad.
// So let's use angleDelta instead.
if (delta == 0) {
- delta = wheelEvent->angleDelta().y();
+ delta = wheelEvent->angleDelta().x();
}
// Invert the value if necessary.
@@ -277,53 +309,143 @@ MenuEventFilter::MenuEventFilter(QMenu* menu)
bool MenuEventFilter::eventFilter(QObject* watchedObject, QEvent* evt) {
const auto type = evt->type();
- if (type == QEvent::Type::Show) {
- // Place the QMenu correctly by making up for the drop shadow margins.
- // It'll be reset before every show, so we can safely move it every time.
- // Submenus should already be placed correctly, so there's no need to translate their geometry.
- // Also, make up for the menu item padding so the texts are aligned.
- const auto isMenuBarMenu = qobject_cast(_menu->parentWidget()) != nullptr;
- const auto isSubMenu = qobject_cast(_menu->parentWidget()) != nullptr;
- const auto alignForMenuBar = isMenuBarMenu && !isSubMenu;
- const auto* qlementineStyle = qobject_cast(_menu->style());
- const auto menuItemHPadding = qlementineStyle ? qlementineStyle->theme().spacing : 0;
- const auto menuDropShadowWidth = qlementineStyle ? qlementineStyle->theme().spacing : 0;
- const auto menuOriginalPos = _menu->pos();
- const auto menuBarTranslation = alignForMenuBar ? QPoint(-menuItemHPadding, 0) : QPoint(0, 0);
- const auto shadowTranslation = QPoint(-menuDropShadowWidth, -menuDropShadowWidth);
- const auto menuNewPos = menuOriginalPos + menuBarTranslation + shadowTranslation;
-
- // Menus have weird sizing bugs when moving them from this event.
- // We have to wait for the event loop to be processed before setting the final position.
- const auto menuSize = _menu->size();
- if (menuSize != QSize(0, 0)) {
- _menu->resize(0, 0); // Hide the menu for now until we can set the position.
- QTimer::singleShot(0, _menu, [this, menuNewPos, menuSize]() {
- _menu->move(menuNewPos);
- _menu->resize(menuSize);
- });
- }
+
+ switch (type) {
+ case QEvent::Type::Show: {
+ // Place the QMenu correctly by making up for the drop shadow margins.
+ // It'll be reset before every show, so we can safely move it every time.
+ // Submenus should already be placed correctly, so there's no need to translate their geometry.
+ // Also, make up for the menu item padding so the texts are aligned.
+ const auto isMenuBarMenu = qobject_cast(_menu->parentWidget()) != nullptr;
+ const auto isSubMenu = qobject_cast(_menu->parentWidget()) != nullptr;
+ const auto alignForMenuBar = isMenuBarMenu && !isSubMenu;
+ const auto* qlementineStyle = qobject_cast(_menu->style());
+ const auto menuItemHPadding = qlementineStyle ? qlementineStyle->theme().spacing : 0;
+ const auto menuDropShadowWidth = qlementineStyle ? qlementineStyle->theme().spacing : 0;
+ const auto menuOriginalPos = _menu->pos();
+ const auto menuBarTranslation = alignForMenuBar ? QPoint(-menuItemHPadding, 0) : QPoint(0, 0);
+ const auto shadowTranslation = QPoint(-menuDropShadowWidth, -menuDropShadowWidth);
+ const auto menuNewPos = menuOriginalPos + menuBarTranslation + shadowTranslation;
+
+ // Menus have weird sizing bugs when moving them from this event.
+ // We have to wait for the event loop to be processed before setting the final position.
+ const auto menuSize = _menu->size();
+ if (menuSize != QSize(0, 0)) {
+ _menu->resize(0, 0); // Hide the menu for now until we can set the position.
+ QTimer::singleShot(0, _menu, [this, menuNewPos, menuSize]() {
+ _menu->move(menuNewPos);
+ _menu->resize(menuSize);
+ });
+ }
+ } break;
+ case QEvent::Type::MouseButtonPress: {
+ const auto* mouseEvt = static_cast(evt);
+ const auto mousePos = mouseEvt->pos();
+ if (const auto* action = _menu->actionAt(mousePos)) {
+ if (action->isSeparator() || !action->isEnabled() || action->property("qlementine_flashing").toBool()) {
+ return true;
+ }
+ } else if (_menu->rect().contains(mousePos)) {
+ return true;
+ }
+ } break;
+ case QEvent::Type::MouseButtonRelease: {
+ const auto* mouseEvt = static_cast(evt);
+ const auto mousePos = mouseEvt->pos();
+ if (auto* action = _menu->actionAt(mousePos)) {
+ if (action->isSeparator() || !action->isEnabled() || action->property("qlementine_flashing").toBool())
+ return true;
+
+ if (action->menu() == nullptr) {
+ flashAction(action, _menu, [this, action]() {
+ // The call to QAction::trigger might destroy the menu or the actions.
+ const QPointer menu_guard(_menu);
+ action->trigger();
+ if (menu_guard) {
+ if (auto* top_menu = getTopLevelMenu(menu_guard)) {
+ top_menu->close();
+ }
+ }
+ });
+ return true;
+ }
+ } else if (_menu->rect().contains(mousePos)) {
+ return true;
+ }
+ } break;
+ default:
+ break;
}
return QObject::eventFilter(watchedObject, evt);
}
-ComboboxItemViewFilter::ComboboxItemViewFilter(QAbstractItemView* view)
+ComboboxItemViewFilter::ComboboxItemViewFilter(QComboBox* comboBox, QListView* view)
: QObject(view)
+ , _comboBox(comboBox)
, _view(view) {
- view->installEventFilter(this);
+ _view->installEventFilter(this);
+
+ auto* comboBoxPopup = _view->parentWidget();
+ comboBoxPopup->installEventFilter(this);
+
+ const auto childWidgets = comboBoxPopup->findChildren();
+ for (auto* child : childWidgets) {
+ if (child->inherits("QComboBoxPrivateScroller")) {
+ child->setFixedHeight(0);
+ child->setVisible(false);
+ }
+ }
+
+ _comboBox->installEventFilter(this);
}
bool ComboboxItemViewFilter::eventFilter(QObject* watchedObject, QEvent* evt) {
const auto type = evt->type();
- if (type == QEvent::Type::Show) {
- // Fix Qt bug.
- const auto width = _view->sizeHintForColumn(0);
- _view->setMinimumWidth(width);
+ switch (type) {
+ case QEvent::Type::Show:
+ fixViewGeometry();
+ break;
+ case QEvent::Type::Resize:
+ if (watchedObject == _comboBox) {
+ fixViewGeometry();
+ }
+ break;
+ default:
+ break;
}
return QObject::eventFilter(watchedObject, evt);
}
+void ComboboxItemViewFilter::fixViewGeometry() {
+ const auto* comboBox = findFirstParentOfType(_view);
+ const auto* qlementineStyle = qobject_cast(comboBox->style());
+ const auto hMargin = qlementineStyle->pixelMetric(QStyle::PM_MenuHMargin);
+ const auto shadowWidth = qlementineStyle->theme().spacing;
+ const auto borderWidth = qlementineStyle->theme().borderWidth;
+ const auto width =
+ std::max(comboBox->width(), _view->sizeHintForColumn(0) + shadowWidth * 2) + hMargin * 2 + borderWidth * 2;
+ const auto height = viewMinimumSizeHint().height();
+ _view->setFixedWidth(width);
+ _view->setFixedHeight(height);
+ _view->parentWidget()->adjustSize();
+}
+
+QSize ComboboxItemViewFilter::viewMinimumSizeHint() const {
+ // QListView::minimumSizeHint() doesn't give the correct minimumHeight,
+ // so we have to compute it.
+ const auto rowCount = _view->model()->rowCount();
+ const auto maxHeight = _view->maximumHeight();
+ auto height = 0;
+ for (auto i = 0; i < rowCount && height <= maxHeight; ++i) {
+ const auto rowSizeHint = _view->sizeHintForRow(i);
+ height = std::min(maxHeight, height + rowSizeHint);
+ }
+ // It looks like it is OK for the width, though.
+ const auto width = _view->sizeHintForColumn(0);
+ return { width, height };
+}
+
TextEditEventFilter::TextEditEventFilter(QAbstractScrollArea* textEdit)
: QObject(textEdit)
, _textEdit(textEdit) {}
@@ -367,10 +489,9 @@ bool TextEditEventFilter::eventFilter(QObject* watchedObject, QEvent* evt) {
return QObject::eventFilter(watchedObject, evt);
}
-WidgetWithFocusFrameEventFilter::WidgetWithFocusFrameEventFilter(QWidget* widget):
- QObject(widget),
- _widget(widget) {
-}
+WidgetWithFocusFrameEventFilter::WidgetWithFocusFrameEventFilter(QWidget* widget)
+ : QObject(widget)
+ , _widget(widget) {}
bool WidgetWithFocusFrameEventFilter::eventFilter(QObject* watchedObject, QEvent* evt) {
if (watchedObject == _widget) {
@@ -387,4 +508,127 @@ bool WidgetWithFocusFrameEventFilter::eventFilter(QObject* watchedObject, QEvent
return QObject::eventFilter(watchedObject, evt);
}
+class LineEditMenuIconsBehavior : public QObject {
+ QPointer _menu{ nullptr };
+ bool _menuCustomized{ false };
+
+ enum class IconListMode {
+ None,
+ LineEdit,
+ ReadOnlyLineEdit,
+ SpinBox,
+ };
+
+ static std::vector iconList(IconListMode mode) {
+ const auto* qlem = oclero::qlementine::appStyle();
+ if (!qlem)
+ return {};
+
+ // The order follows the one defined QLineEdit.cpp and QSpinBox.cpp (Qt6).
+ switch (mode) {
+ case IconListMode::LineEdit:
+ return {
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-undo"),
+ qlem->makeThemedIconFromName("edit-redo"),
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-cut"),
+ qlem->makeThemedIconFromName("edit-copy"),
+ qlem->makeThemedIconFromName("edit-paste"),
+ qlem->makeThemedIconFromName("edit-delete"),
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-select-all"),
+ };
+ case IconListMode::ReadOnlyLineEdit:
+ return {
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-copy"),
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-select-all"),
+ };
+ case IconListMode::SpinBox:
+ return {
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-undo"),
+ qlem->makeThemedIconFromName("edit-redo"),
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-cut"),
+ qlem->makeThemedIconFromName("edit-copy"),
+ qlem->makeThemedIconFromName("edit-paste"),
+ qlem->makeThemedIconFromName("edit-delete"),
+ QIcon(), // Separator
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("edit-select-all"),
+ QIcon(), // Separator
+ qlem->makeThemedIconFromName("go-up"),
+ qlem->makeThemedIconFromName("go-down"),
+ };
+ default:
+ return {};
+ }
+ }
+
+ static IconListMode getMode(const QMenu* menu) {
+ if (const auto* menu_parent = menu->parent()) {
+ if (qobject_cast(menu_parent->parent())) {
+ return IconListMode::SpinBox;
+ } else if (const auto* line_edit = qobject_cast(menu_parent)) {
+ return line_edit->isReadOnly() ? IconListMode::ReadOnlyLineEdit : IconListMode::LineEdit;
+ }
+ }
+ return IconListMode::None;
+ }
+
+ void customizeMenu() {
+ const auto actions = _menu->findChildren();
+ if (!actions.empty()) {
+ const auto icons = iconList(getMode(_menu));
+ if (!icons.empty()) {
+ for (auto i = 0; i < static_cast(icons.size()) && i < static_cast(actions.size()); ++i) {
+ if (auto* action = actions.at(i)) {
+ action->setIcon(icons.at(i));
+ }
+ }
+ }
+ }
+ _menu->adjustSize();
+ }
+
+public:
+ LineEditMenuIconsBehavior(QMenu* menu)
+ : QObject(menu)
+ , _menu(menu) {
+ // Hack pour modifier les icones du menu contextuel des line edit.
+ QObject::connect(_menu, &QMenu::aboutToShow, this, [this]() {
+ if (!_menuCustomized) {
+ customizeMenu();
+ _menuCustomized = true;
+ }
+ });
+ }
+};
+
+LineEditMenuEventFilter::LineEditMenuEventFilter(QWidget* parent)
+ : QObject(parent) {
+ assert(parent);
+ if (auto* menu = qobject_cast(parent)) {
+ new LineEditMenuIconsBehavior(menu);
+ } else {
+ parent->installEventFilter(this);
+ }
+}
+
+bool LineEditMenuEventFilter::eventFilter(QObject*, QEvent* evt) {
+ const auto type = evt->type();
+ if (type == QEvent::ChildPolished) {
+ auto* child = static_cast(evt)->child();
+ if (auto* lineedit = qobject_cast(child)) {
+ lineedit->installEventFilter(this);
+ } else if (auto* menu = qobject_cast(child)) {
+ new LineEditMenuIconsBehavior(menu);
+ }
+ }
+
+ return false;
+}
} // namespace oclero::qlementine
diff --git a/lib/src/style/EventFilters.hpp b/lib/src/style/EventFilters.hpp
index 2058eb5..a863647 100644
--- a/lib/src/style/EventFilters.hpp
+++ b/lib/src/style/EventFilters.hpp
@@ -8,20 +8,22 @@
#include
#include
-#include
+#include
+#include
#include
+#include
class QFocusFrame;
namespace oclero::qlementine {
class LineEditButtonEventFilter : public QObject {
public:
- LineEditButtonEventFilter(QlementineStyle& style, WidgetAnimationManager& animManager, QToolButton* button);
+ LineEditButtonEventFilter(QlementineStyle* style, WidgetAnimationManager& animManager, QToolButton* button);
bool eventFilter(QObject* watchedObject, QEvent* evt) override;
private:
- QlementineStyle& _style;
+ QPointer _style;
WidgetAnimationManager& _animManager;
QToolButton* _button{ nullptr };
};
@@ -73,12 +75,15 @@ class MenuEventFilter : public QObject {
class ComboboxItemViewFilter : public QObject {
public:
- ComboboxItemViewFilter(QAbstractItemView* view);
+ ComboboxItemViewFilter(QComboBox* comboBox, QListView* view);
bool eventFilter(QObject* watchedObject, QEvent* evt) override;
private:
- QAbstractItemView* _view{ nullptr };
+ void fixViewGeometry();
+ QSize viewMinimumSizeHint() const;
+ QComboBox* _comboBox{ nullptr };
+ QListView* _view{ nullptr };
};
// Works for both QTextEdit and QPlainTextEdit
@@ -104,4 +109,11 @@ class WidgetWithFocusFrameEventFilter : public QObject {
QFocusFrame* _focusFrame{ nullptr };
};
+class LineEditMenuEventFilter : public QObject {
+public:
+ LineEditMenuEventFilter(QWidget* parent);
+
+protected:
+ virtual bool eventFilter(QObject* obj, QEvent* evt) override;
+};
} // namespace oclero::qlementine
diff --git a/lib/src/style/QlementineStyle.cpp b/lib/src/style/QlementineStyle.cpp
index 855c996..a2ad7d5 100644
--- a/lib/src/style/QlementineStyle.cpp
+++ b/lib/src/style/QlementineStyle.cpp
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -52,11 +53,18 @@
#include
#include
#include
+#include
+#include
#include
#include
namespace oclero::qlementine {
+
+QlementineStyle* appStyle() {
+ return qobject_cast(qApp->style());
+}
+
/// Used to initializeResources from .qrc only once.
std::once_flag qlementineOnceFlag;
@@ -211,20 +219,42 @@ struct QlementineStyleImpl {
return QMargins(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
+ /// Makes an IconTheme from the Theme.
+ IconTheme iconThemeFromTheme(ColorRole role = ColorRole::Secondary) const {
+ switch (role) {
+ case ColorRole::Primary:
+ return {
+ owner.iconForegroundColor(MouseState::Normal, ColorRole::Primary),
+ owner.iconForegroundColor(MouseState::Hovered, ColorRole::Primary),
+ owner.iconForegroundColor(MouseState::Pressed, ColorRole::Primary),
+ owner.iconForegroundColor(MouseState::Disabled, ColorRole::Primary),
+ };
+ case ColorRole::Secondary:
+ default:
+ return {
+ owner.iconForegroundColor(MouseState::Normal, ColorRole::Secondary),
+ owner.iconForegroundColor(MouseState::Hovered, ColorRole::Secondary),
+ owner.iconForegroundColor(MouseState::Pressed, ColorRole::Secondary),
+ owner.iconForegroundColor(MouseState::Disabled, ColorRole::Secondary),
+ };
+ }
+ }
+
QlementineStyle& owner;
- Theme theme;
+ Theme theme{};
std::unique_ptr fontMetricsBold{ nullptr };
WidgetAnimationManager animations;
std::unordered_map standardIconCache;
std::unordered_map standardIconExtCache;
- bool useMenuForComboBoxPopup{ false };
AutoIconColor autoIconColor{ AutoIconColor::None };
+ std::function iconPathFunc;
};
QlementineStyle::QlementineStyle(QObject* parent)
: _impl(new QlementineStyleImpl{ *this }) {
setParent(parent);
setObjectName(QStringLiteral("QlementineStyle"));
+ triggerCompleteRepaint();
}
QlementineStyle::~QlementineStyle() = default;
@@ -243,8 +273,10 @@ void QlementineStyle::setTheme(Theme const& theme) {
}
void QlementineStyle::setThemeJsonPath(QString const& jsonPath) {
- const auto theme = Theme(jsonPath);
- setTheme(theme);
+ const auto themeOpt = Theme::fromJsonPath(jsonPath);
+ if (themeOpt.has_value()) {
+ setTheme(themeOpt.value());
+ }
}
bool QlementineStyle::animationsEnabled() const {
@@ -259,17 +291,6 @@ void QlementineStyle::setAnimationsEnabled(bool enabled) {
}
}
-bool QlementineStyle::useMenuForComboBoxPopup() const {
- return _impl->useMenuForComboBoxPopup;
-}
-
-void QlementineStyle::setUseMenuForComboBoxPopup(bool useMenu) {
- if (useMenu != _impl->useMenuForComboBoxPopup) {
- _impl->useMenuForComboBoxPopup = useMenu;
- emit useMenuForComboBoxPopupChanged();
- }
-}
-
void QlementineStyle::triggerCompleteRepaint() {
_impl->updateFonts();
_impl->updatePalette();
@@ -336,21 +357,22 @@ QPixmap QlementineStyle::getColorizedPixmap(
return input;
}
-QIcon QlementineStyle::makeIcon(const QString& svgPath) {
- QIcon result;
- QPixmap pixmap(svgPath);
-
- result.addPixmap(pixmap, QIcon::Normal, QIcon::Off);
- result.addPixmap(pixmap, QIcon::Disabled, QIcon::Off);
- result.addPixmap(pixmap, QIcon::Active, QIcon::Off);
- result.addPixmap(pixmap, QIcon::Selected, QIcon::Off);
+QIcon QlementineStyle::makeThemedIcon(const QString& svgPath, const QSize& size, ColorRole role) const {
+ const auto iconTheme = _impl->iconThemeFromTheme(role);
+ return makeIconFromSvg(svgPath, iconTheme, size);
+}
- result.addPixmap(pixmap, QIcon::Normal, QIcon::On);
- result.addPixmap(pixmap, QIcon::Disabled, QIcon::On);
- result.addPixmap(pixmap, QIcon::Active, QIcon::On);
- result.addPixmap(pixmap, QIcon::Selected, QIcon::On);
+QIcon QlementineStyle::makeThemedIconFromName(const QString& name, const QSize& size, ColorRole role) const {
+ if (_impl->iconPathFunc) {
+ const auto iconPath = _impl->iconPathFunc(name);
+ return makeThemedIcon(iconPath, size, role);
+ } else {
+ return QIcon::fromTheme(name);
+ }
+}
- return result;
+void QlementineStyle::setIconPathGetter(const std::function& func) {
+ _impl->iconPathFunc = func;
}
/* QStyle overrides. */
@@ -405,8 +427,17 @@ void QlementineStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption* opt
break;
case PE_FrameMenu:
return; // Let PE_PanelMenu do the drawing.
- case PE_FrameStatusBarItem:
- break;
+ case PE_FrameStatusBarItem: {
+ const auto rect = opt->rect;
+ const auto penColor = _impl->theme.borderColor;
+ const auto penWidth = _impl->theme.borderWidth;
+ const auto p1 = QPoint{ rect.x() + 1 + penWidth, rect.y() + rect.x() };
+ const auto p2 = QPoint{ rect.x() + 1 + penWidth, rect.y() + rect.height() };
+ p->setPen(QPen(penColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p->setBrush(Qt::NoBrush);
+ p->drawLine(p1, p2);
+ }
+ return;
case PE_FrameTabWidget: {
// QTabWidget.cpp, line 1296, in QTabWidget::paintEvent():
// The widget does not draw the Tab bar background unless it's in
@@ -417,8 +448,9 @@ void QlementineStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption* opt
const auto* tabBar = tabWidget ? tabWidget->tabBar() : nullptr;
if (!documentMode && tabBar) {
// Draw a border around the content.
+ const auto mouse = getMouseState(opt->state);
const auto radius = _impl->theme.borderRadius * 1.5;
- const auto& borderColor = tabBarBackgroundColor();
+ const auto borderColor = tabBarBackgroundColor(mouse);
const auto borderW = _impl->theme.borderWidth;
drawRoundedRectBorder(
p, opt->rect.adjusted(0, -borderW, 0, 0), borderColor, borderW, RadiusesF(0., 0., radius, radius));
@@ -458,11 +490,13 @@ void QlementineStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption* opt
}
case PE_FrameTabBarBase:
if (const auto* optTabBar = qstyleoption_cast(opt)) {
+ const auto mouse = getMouseState(opt->state);
+ const auto& bgColor = tabBarBackgroundColor(mouse);
if (optTabBar->documentMode) {
- p->fillRect(opt->rect, tabBarBackgroundColor());
+ p->fillRect(opt->rect, bgColor);
} else {
const auto radius = _impl->theme.borderRadius * 1.5;
- drawRoundedRect(p, opt->rect, tabBarBackgroundColor(), RadiusesF(radius, radius, 0., 0.));
+ drawRoundedRect(p, opt->rect, bgColor, RadiusesF(radius, radius, 0., 0.));
}
}
return;
@@ -798,7 +832,8 @@ void QlementineStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption* opt
// Filled rectangle below scroll buttons.
// We need to fill the whole surface to ensure tabs are not visible below.
- const auto& tabBarBgColor = tabBarBackgroundColor();
+ const auto mouse = getMouseState(opt->state);
+ const auto& tabBarBgColor = tabBarBackgroundColor(mouse);
const auto filledRect = QRect(rect.x() + rect.width() - scrollButtonsW, rect.y(), scrollButtonsW, rect.height());
drawRoundedRect(p, filledRect, tabBarBgColor, documentMode ? 0. : RadiusesF(0., radius, 0., 0.));
}
@@ -1399,6 +1434,7 @@ void QlementineStyle::drawControl(ControlElement ce, const QStyleOption* opt, QP
}
// Icon.
+ const auto iconSpace = optMenuItem->maxIconWidth > 0 ? optMenuItem->maxIconWidth + spacing : 0;
const auto pixmap = getPixmap(optMenuItem->icon, _impl->theme.iconSize, mouse, checkState, w);
if (!pixmap.isNull()) {
const auto& colorizedPixmap = getColorizedPixmap(pixmap, autoIconColor(w), fgColor, fgColor);
@@ -1409,11 +1445,9 @@ void QlementineStyle::drawControl(ControlElement ce, const QStyleOption* opt, QP
const auto pixmapY = fgRect.y() + (fgRect.height() - pixmapH) / 2;
const auto pixmapRect = QRect{ pixmapX, pixmapY, pixmapW, pixmapH };
p->drawPixmap(pixmapRect, colorizedPixmap);
-
- const auto taken = pixmapW + spacing;
- availableW -= taken;
- availableX += taken;
}
+ availableW -= iconSpace;
+ availableX += iconSpace;
// Shortcut text.
if (!shortcut.isEmpty()) {
@@ -1789,33 +1823,39 @@ void QlementineStyle::drawControl(ControlElement ce, const QStyleOption* opt, QP
break;
case CE_SizeGrip:
break;
- case CE_Splitter:
- break;
- case CE_RubberBand:
- break;
- case CE_DockWidgetTitle:
- break;
- case CE_ScrollBarAddLine:
- // TODO
- break;
- case CE_ScrollBarSubLine:
- // TODO
- break;
- case CE_ScrollBarAddPage:
- // TODO
- break;
- case CE_ScrollBarSubPage:
- // TODO
- break;
- case CE_ScrollBarSlider:
- // TODO
- break;
- case CE_ScrollBarFirst:
- // TODO
- break;
- case CE_ScrollBarLast:
- // TODO
- break;
+ case CE_Splitter: {
+ const auto mouse = getMouseState(opt->state);
+ const auto& lineColor = splitterColor(mouse);
+ // const auto currentLineColor = _impl->animations.animateBackgroundColor(w, lineColor, _impl->theme.animationDuration);
+ const auto line_rect = opt->rect.adjusted(-1, 0, 1, 0);
+ p->fillRect(line_rect, lineColor);
+ }
+ return;
+ // case CE_RubberBand:
+ // break;
+ // case CE_DockWidgetTitle:
+ // break;
+ // case CE_ScrollBarAddLine:
+ // // TODO
+ // break;
+ // case CE_ScrollBarSubLine:
+ // // TODO
+ // break;
+ // case CE_ScrollBarAddPage:
+ // // TODO
+ // break;
+ // case CE_ScrollBarSubPage:
+ // // TODO
+ // break;
+ // case CE_ScrollBarSlider:
+ // // TODO
+ // break;
+ // case CE_ScrollBarFirst:
+ // // TODO
+ // break;
+ // case CE_ScrollBarLast:
+ // // TODO
+ // break;
case CE_FocusFrame:
if (const auto* focusFrame = qobject_cast(w)) {
const auto* monitoredWidget = focusFrame->widget();
@@ -2828,11 +2868,13 @@ void QlementineStyle::drawComplexControl(
// Draw an opaque background to hide tabs below.
const auto isLeftButton = toolbuttonOpt->arrowType == Qt::ArrowType::LeftArrow;
+ const auto tabBarState = parentTabBar->isEnabled() ? MouseState::Normal : MouseState::Disabled;
if (parentTabBar->documentMode() || isLeftButton) {
- p->fillRect(toolbuttonOpt->rect, tabBarBackgroundColor());
+ p->fillRect(toolbuttonOpt->rect, tabBarBackgroundColor(tabBarState));
} else {
const auto bgRadius = _impl->theme.borderRadius * 1.5;
- drawRoundedRect(p, toolbuttonOpt->rect, tabBarBackgroundColor(), RadiusesF(0., bgRadius, 0., 0.));
+ drawRoundedRect(
+ p, toolbuttonOpt->rect, tabBarBackgroundColor(tabBarState), RadiusesF(0., bgRadius, 0., 0.));
}
// Rect.
@@ -3261,7 +3303,9 @@ QRect QlementineStyle::subControlRect(
if (comboBoxOpt->editable) {
const auto indicatorSize = _impl->theme.iconSize;
const auto spacing = _impl->theme.spacing;
- if (qobject_cast(w) != nullptr) {
+ const auto isBasicComboBox =
+ qobject_cast(w) != nullptr && qobject_cast(w) == nullptr;
+ if (isBasicComboBox) {
// Strange hack to place the QLineEdit correctly.
const auto indicatorButtonW = spacing * 2 + indicatorSize.width();
const auto shiftX = static_cast(spacing * 2.5);
@@ -3284,8 +3328,17 @@ QRect QlementineStyle::subControlRect(
const auto frameY = comboBoxOpt->rect.y() + (comboBoxOpt->rect.height() - frameH) / 2;
return QRect{ frameX, frameY, frameW, frameH };
} break;
- case SC_ComboBoxListBoxPopup:
- return opt->rect;
+ case SC_ComboBoxListBoxPopup: {
+ const auto contentMarginH = pixelMetric(PM_MenuHMargin);
+ const auto contentMarginV = pixelMetric(PM_MenuVMargin);
+ const auto shadowWidth = _impl->theme.spacing;
+ const auto borderWidth = _impl->theme.borderWidth;
+ const auto width = std::max(opt->rect.width(), w->width());
+ const auto height = opt->rect.height() + 12; // Not possible to change height here.
+ const auto x = opt->rect.x() - shadowWidth - borderWidth - contentMarginH;
+ const auto y = opt->rect.y() - shadowWidth - borderWidth - contentMarginV / 2; // TODO remove hardcoded
+ return { x, y, width, height };
+ } break;
default:
break;
}
@@ -3858,10 +3911,12 @@ QSize QlementineStyle::sizeFromContents(
break;
case CT_SpinBox:
if (const auto* optSpinbox = qstyleoption_cast(opt)) {
+ const auto isDateTimeEdit = qobject_cast(widget) != nullptr;
const auto hasButtons = optSpinbox->buttonSymbols != QAbstractSpinBox::NoButtons;
- const auto buttonW = hasButtons ? _impl->theme.controlHeightLarge : 0;
+ const auto buttonW = isDateTimeEdit || hasButtons ? _impl->theme.controlHeightLarge : 0;
+ const auto dateTimeWidth = isDateTimeEdit ? _impl->theme.iconSize.width() : 0;
const auto borderW = optSpinbox->frame ? pixelMetric(PM_SpinBoxFrameWidth, opt, widget) : 0;
- return QSize{ contentSize.width() + buttonW + 2 * borderW, _impl->theme.controlHeightLarge };
+ return QSize{ contentSize.width() + buttonW + dateTimeWidth + 2 * borderW, _impl->theme.controlHeightLarge };
}
break;
case CT_SizeGrip:
@@ -4039,8 +4094,7 @@ int QlementineStyle::pixelMetric(PixelMetric m, const QStyleOption* opt, const Q
// Splitter.
case PM_SplitterWidth:
- break;
-
+ return 1;
// TitleBar.
case PM_TitleBarHeight:
break;
@@ -4054,9 +4108,11 @@ int QlementineStyle::pixelMetric(PixelMetric m, const QStyleOption* opt, const Q
// Scroller is the part where the user can click to scroll the menu when it is too big.
return _impl->theme.controlHeightSmall;
case PM_MenuHMargin:
- case PM_MenuVMargin:
+ case PM_MenuVMargin: {
// Keep some space between the items and the frame.
- return _impl->theme.spacing;
+ const auto borderW = qobject_cast(w) ? 1 : 0;
+ return _impl->theme.spacing / 2 + borderW;
+ }
case PM_MenuPanelWidth:
// Keep some space for drop shadow.
return _impl->theme.spacing;
@@ -4337,17 +4393,13 @@ int QlementineStyle::styleHint(StyleHint sh, const QStyleOption* opt, const QWid
case SH_ComboBox_ListMouseTracking:
return true;
case SH_ComboBox_Popup:
- // This changes the way the dropdown popup behaves.
- // A different QItemDelegate will be used to size/draw the items.
- // - true: not animated, uses QComboBoxMenuDelegate, that calls QStyle::drawControl(CE_MenuItem)
- // - false: animated, uses QComboBoxDelegate, that just calls QItemDelegate::sizeHint()/paint()
- return _impl->useMenuForComboBoxPopup;
+ return true;
case SH_ComboBox_LayoutDirection:
break;
case SH_ComboBox_PopupFrameStyle:
return QFrame::StyledPanel | QFrame::Plain;
- case SH_ComboBox_UseNativePopup: // Only on MacOS.
- return true;
+ case SH_ComboBox_UseNativePopup:
+ return false;
case SH_ComboBox_AllowWheelScrolling:
return false;
@@ -4455,7 +4507,7 @@ int QlementineStyle::styleHint(StyleHint sh, const QStyleOption* opt, const QWid
case SH_LineEdit_PasswordCharacter:
return QChar(0x2022).unicode(); // Bullet.
case SH_LineEdit_PasswordMaskDelay:
- return 200;
+ return 0;
// FocusFrame
case SH_FocusFrame_AboveWidget:
@@ -4530,144 +4582,6 @@ QIcon QlementineStyle::standardIcon(StandardPixmap sp, const QStyleOption* opt,
case SP_ArrowRight:
case SP_LineEditClearButton:
return _impl->getStandardIcon(sp, _impl->theme.iconSize);
- // case SP_TitleBarMenuButton:
- // break;
- // case SP_TitleBarMinButton:
- // break;
- // case SP_TitleBarMaxButton:
- // break;
- // case SP_TitleBarCloseButton:
- // break;
- // case SP_TitleBarNormalButton:
- // break;
- // case SP_TitleBarShadeButton:
- // break;
- // case SP_TitleBarUnshadeButton:
- // break;
- // case SP_TitleBarContextHelpButton:
- // break;
- // case SP_DockWidgetCloseButton:
- // break;
- // case SP_DesktopIcon:
- // break;
- // case SP_TrashIcon:
- // break;
- // case SP_ComputerIcon:
- // break;
- // case SP_DriveFDIcon:
- // break;
- // case SP_DriveHDIcon:
- // break;
- // case SP_DriveCDIcon:
- // break;
- // case SP_DriveDVDIcon:
- // break;
- // case SP_DriveNetIcon:
- // break;
- // case SP_DirOpenIcon:
- // break;
- // case SP_DirClosedIcon:
- // break;
- // case SP_DirLinkIcon:
- // break;
- // case SP_DirLinkOpenIcon:
- // break;
- // case SP_FileIcon:
- // break;
- // case SP_FileLinkIcon:
- // break;
- // case SP_FileDialogStart:
- // break;
- // case SP_FileDialogEnd:
- // break;
- // case SP_FileDialogToParent:
- // break;
- // case SP_FileDialogNewFolder:
- // break;
- // case SP_FileDialogDetailedView:
- // break;
- // case SP_FileDialogInfoView:
- // break;
- // case SP_FileDialogContentsView:
- // break;
- // case SP_FileDialogListView:
- // break;
- // case SP_FileDialogBack:
- // break;
- // case SP_DirIcon:
- // break;
- // case SP_DialogOkButton:
- // break;
- // case SP_DialogCancelButton:
- // break;
- // case SP_DialogHelpButton:
- // break;
- // case SP_DialogOpenButton:
- // break;
- // case SP_DialogSaveButton:
- // break;
- // case SP_DialogCloseButton:
- // break;
- // case SP_DialogApplyButton:
- // break;
- // case SP_DialogResetButton:
- // break;
- // case SP_DialogDiscardButton:
- // break;
- // case SP_DialogYesButton:
- // break;
- // case SP_DialogNoButton:
- // break;
- // case SP_DialogYesToAllButton:
- // break;
- // case SP_DialogNoToAllButton:
- // break;
- // case SP_DialogSaveAllButton:
- // break;
- // case SP_DialogAbortButton:
- // break;
- // case SP_DialogRetryButton:
- // break;
- // case SP_DialogIgnoreButton:
- // break;
- // case SP_ArrowUp:
- // break;
- // case SP_ArrowDown:
- // break;
- // case SP_ArrowBack:
- // break;
- // case SP_ArrowForward:
- // break;
- // case SP_DirHomeIcon:
- // break;
- // case SP_CommandLink:
- // break;
- // case SP_VistaShield:
- // break;
- // case SP_BrowserReload:
- // break;
- // case SP_BrowserStop:
- // break;
- // case SP_MediaPlay:
- // break;
- // case SP_MediaStop:
- // break;
- // case SP_MediaPause:
- // break;
- // case SP_MediaSkipForward:
- // break;
- // case SP_MediaSkipBackward:
- // break;
- // case SP_MediaSeekForward:
- // break;
- // case SP_MediaSeekBackward:
- // break;
- // case SP_MediaVolume:
- // break;
- // case SP_MediaVolumeMuted:
- // break;
- // case SP_RestoreDefaultsButton:
- // break;
default:
break;
}
@@ -4700,6 +4614,8 @@ void QlementineStyle::polish(QApplication* app) {
QCommonStyle::polish(app);
app->setFont(_impl->theme.fontRegular);
//app->installEventFilter(new AppEventFilter(app));
+
+ QApplication::setAttribute(Qt::ApplicationAttribute::AA_DontShowIconsInMenus, false);
}
void QlementineStyle::unpolish(QApplication* app) {
@@ -4724,7 +4640,7 @@ void QlementineStyle::polish(QWidget* w) {
// Special case for the Qt-private buttons in a QLineEdit.
if (w->inherits("QLineEditIconButton")) {
- w->installEventFilter(new LineEditButtonEventFilter(*this, _impl->animations, qobject_cast(w)));
+ w->installEventFilter(new LineEditButtonEventFilter(this, _impl->animations, qobject_cast(w)));
w->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
// Fix hardcoded width in qlineedit_p.cpp:493
w->setFixedSize(_impl->theme.controlHeightMedium, _impl->theme.controlHeightMedium);
@@ -4778,25 +4694,26 @@ void QlementineStyle::polish(QWidget* w) {
}
// Try to remove the background...
- if (auto* itemView = qobject_cast(w)) {
- auto* parent = itemView->parentWidget();
- auto isComboBoxPopupContainer = parent && parent->inherits("QComboBoxPrivateContainer");
+ if (auto* itemView = qobject_cast(w)) {
+ auto* popup = itemView->parentWidget();
+ auto isComboBoxPopupContainer = popup && popup->inherits("QComboBoxPrivateContainer");
if (isComboBoxPopupContainer) {
- itemView->setBackgroundRole(QPalette::NoRole);
- itemView->viewport()->setBackgroundRole(QPalette::NoRole);
- parent->setBackgroundRole(QPalette::NoRole);
- parent->setAutoFillBackground(false);
- parent->setAttribute(Qt::WA_TranslucentBackground, true);
- parent->setAttribute(Qt::WA_OpaquePaintEvent, false);
- parent->setAttribute(Qt::WA_NoSystemBackground, true);
- itemView->installEventFilter(new ComboboxItemViewFilter(itemView));
- if (auto* scrollArea = parent->findChild()) {
- scrollArea->setBackgroundRole(QPalette::NoRole);
- scrollArea->setAutoFillBackground(false);
- scrollArea->setAttribute(Qt::WA_TranslucentBackground, true);
- scrollArea->setAttribute(Qt::WA_OpaquePaintEvent, false);
- scrollArea->setAttribute(Qt::WA_NoSystemBackground, true);
- }
+ popup->setAttribute(Qt::WA_TranslucentBackground, true);
+ popup->setAttribute(Qt::WA_OpaquePaintEvent, false);
+ popup->setAttribute(Qt::WA_NoSystemBackground, true);
+ popup->setWindowFlag(Qt::FramelessWindowHint, true);
+ popup->setWindowFlag(Qt::NoDropShadowWindowHint, true);
+ popup->setProperty("_q_windowsDropShadow", false);
+
+ // Same shadow as QMenu.
+ const auto shadowWidth = _impl->theme.spacing;
+ const auto borderWidth = _impl->theme.borderWidth;
+ const auto margin = shadowWidth + borderWidth;
+ popup->layout()->setContentsMargins(margin, margin, margin, margin);
+
+ itemView->viewport()->setAutoFillBackground(false);
+ auto* comboBox = findFirstParentOfType(itemView);
+ itemView->installEventFilter(new ComboboxItemViewFilter(comboBox, itemView));
}
}
@@ -4861,6 +4778,12 @@ void QlementineStyle::polish(QWidget* w) {
viewport->setAutoFillBackground(false);
}
}
+
+ if (auto* lineEdit = qobject_cast(w)) {
+ lineEdit->installEventFilter(new LineEditMenuEventFilter(lineEdit));
+ } else if (auto* spinBox = qobject_cast(w)) {
+ spinBox->installEventFilter(new LineEditMenuEventFilter(spinBox));
+ }
}
void QlementineStyle::unpolish(QWidget* w) {
@@ -5561,8 +5484,8 @@ QColor const& QlementineStyle::menuBarItemForegroundColor(MouseState const mouse
}
}
-QColor const& QlementineStyle::tabBarBackgroundColor() const {
- return _impl->theme.backgroundColorMain3;
+QColor const& QlementineStyle::tabBarBackgroundColor(MouseState const mouse) const {
+ return mouse == MouseState::Disabled ? _impl->theme.backgroundColorMain3 : _impl->theme.backgroundColorTabBar;
}
QColor const& QlementineStyle::tabBarShadowColor() const {
@@ -5575,18 +5498,21 @@ QColor const& QlementineStyle::tabBarBottomShadowColor() const {
QColor const& QlementineStyle::tabBackgroundColor(MouseState const mouse, SelectionState const selected) const {
const auto isSelected = selected == SelectionState::Selected;
+ const auto& selectedTabColor = _impl->theme.backgroundColorMain2;
+ const auto& hoverTabColor = _impl->theme.neutralColor;
+ const auto& defaultTabColor = _impl->theme.backgroundColorMainTransparent;
switch (mouse) {
case MouseState::Hovered:
- return isSelected ? _impl->theme.backgroundColorMain2 : _impl->theme.neutralColorPressed;
+ return isSelected ? selectedTabColor : hoverTabColor;
case MouseState::Pressed:
- return isSelected ? _impl->theme.backgroundColorMain2 : _impl->theme.secondaryColorPressed;
+ return _impl->theme.backgroundColorMain2;
case MouseState::Normal:
- return isSelected ? _impl->theme.backgroundColorMain2 : _impl->theme.neutralColorTransparent;
+ return isSelected ? selectedTabColor : defaultTabColor;
case MouseState::Disabled:
case MouseState::Transparent:
default:
- return _impl->theme.neutralColorTransparent;
+ return defaultTabColor;
}
}
@@ -5792,6 +5718,14 @@ QColor const& QlementineStyle::labelCaptionForegroundColor(MouseState const mous
return _impl->theme.secondaryAlternativeColor;
}
+QColor const& QlementineStyle::iconForegroundColor(MouseState const mouse, ColorRole const role) const {
+ if (mouse == MouseState::Disabled)
+ return role == ColorRole::Primary ? _impl->theme.primaryColorForegroundDisabled
+ : _impl->theme.secondaryColorForegroundDisabled;
+ else
+ return role == ColorRole::Primary ? _impl->theme.primaryColorForeground : _impl->theme.secondaryColorForeground;
+}
+
QColor const& QlementineStyle::toolBarBackgroundColor() const {
return _impl->theme.backgroundColorMain2;
}
@@ -5855,8 +5789,13 @@ QColor const& QlementineStyle::groupBoxTitleColor(MouseState const mouse, const
return labelForegroundColor(mouse, w);
}
-QColor const& QlementineStyle::groupBoxBackgroundColor(MouseState const mouse) const {
- return mouse == MouseState::Disabled ? _impl->theme.neutralColorTransparent : _impl->theme.neutralColorDisabled;
+QColor QlementineStyle::groupBoxBackgroundColor(MouseState const mouse) const {
+ if (mouse == MouseState::Disabled) {
+ return _impl->theme.backgroundColorMainTransparent;
+ } else {
+ return getColorSourceOver(_impl->theme.backgroundColorMain2,
+ colorWithAlphaF(_impl->theme.backgroundColorMain3, _impl->theme.backgroundColorMain3.alphaF() * .75));
+ }
}
QColor const& QlementineStyle::groupBoxBorderColor(MouseState const mouse) const {
@@ -6090,4 +6029,18 @@ QColor const& QlementineStyle::statusBarBorderColor() const {
QColor const& QlementineStyle::statusBarSeparatorColor() const {
return _impl->theme.secondaryColorDisabled;
}
+
+QColor const& QlementineStyle::splitterColor(const MouseState mouse) const {
+ switch (mouse) {
+ case MouseState::Normal:
+ return _impl->theme.borderColor;
+ case MouseState::Hovered:
+ return _impl->theme.primaryColor;
+ case MouseState::Pressed:
+ return _impl->theme.primaryColorPressed;
+ case MouseState::Disabled:
+ default:
+ return _impl->theme.borderColorTransparent;
+ }
+}
} // namespace oclero::qlementine
diff --git a/lib/src/style/Theme.cpp b/lib/src/style/Theme.cpp
index 529363a..88496bf 100644
--- a/lib/src/style/Theme.cpp
+++ b/lib/src/style/Theme.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
@@ -139,21 +140,39 @@ void setInt(QJsonObject& jsonObj, const QString& key, int value) {
void setDouble(QJsonObject& jsonObj, const QString& key, double value) {
jsonObj.insert(key, value);
}
-} // namespace
-
-Theme::Theme()
- : Theme(QJsonDocument{}) {}
-Theme::Theme(QString const& jsonPath)
- : Theme(readJsonDoc(jsonPath)) {}
+bool jsonObjHasKey(const QJsonObject& obj, const QString& key) {
+ return obj.find(key) != obj.end();
+}
+bool jsonObjHasAllKeys(const QJsonObject& obj, const QVector& keys) {
+ for (const auto& key : keys) {
+ if (!jsonObjHasKey(obj, key))
+ return false;
+ }
+ return true;
+}
+} // namespace
-Theme::Theme(QJsonDocument const& jsonDoc) {
- initializeFromJson(jsonDoc);
+Theme::Theme() {
initializeFonts();
initializePalette();
}
+std::optional Theme::fromJsonPath(const QString& jsonPath) {
+ return fromJsonDoc(readJsonDoc(jsonPath));
+}
+
+std::optional Theme::fromJsonDoc(const QJsonDocument& jsonDoc) {
+ Theme theme;
+ if (theme.initializeFromJson(jsonDoc)) {
+ theme.initializeFonts();
+ theme.initializePalette();
+ return theme;
+ }
+ return std::nullopt;
+}
+
void Theme::initializeFonts() {
// Fonts.
const auto defaultFont = QFont(QStringLiteral("Inter"));
@@ -233,7 +252,7 @@ void Theme::initializePalette() {
palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::WindowText, secondaryColor);
palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::WindowText, secondaryColorDisabled);
palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::PlaceholderText, secondaryColorDisabled);
- palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::PlaceholderText, neutralColorDisabled);
+ palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::PlaceholderText, secondaryColorDisabled);
palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::Link, primaryColor);
palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Link, secondaryColorDisabled);
palette.setColor(QPalette::ColorGroup::All, QPalette::ColorRole::LinkVisited, primaryColor);
@@ -251,160 +270,173 @@ void Theme::initializePalette() {
palette.setColor(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Button, neutralColorDisabled);
}
-void Theme::initializeFromJson(QJsonDocument const& jsonDoc) {
- if (jsonDoc.isObject()) {
- const auto jsonObj = jsonDoc.object();
- if (!jsonObj.isEmpty()) {
- // Parse metadata.
- auto const metaObj = jsonObj.value(QStringLiteral("meta")).toObject();
- meta = ThemeMeta{
- tryGetString(metaObj, QStringLiteral("name"), {}),
- tryGetString(metaObj, QStringLiteral("version"), {}),
- tryGetString(metaObj, QStringLiteral("author"), {}),
- };
-
- // Parse all values.
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain1);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain2);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain3);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain4);
- backgroundColorMainTransparent = colorWithAlpha(backgroundColorMain1, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColorDisabled);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColor);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColorPressed);
- neutralColorTransparent = colorWithAlpha(neutralColorDisabled, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, focusColor);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColor);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorDisabled);
- primaryColorTransparent = colorWithAlpha(primaryColor, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForeground);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForegroundHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForegroundPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForegroundDisabled);
- primaryColorForegroundTransparent = colorWithAlpha(primaryColorForeground, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColor);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColorPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColorDisabled);
- primaryAlternativeColorTransparent = colorWithAlpha(primaryAlternativeColor, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColor);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorDisabled);
- secondaryColorTransparent = colorWithAlpha(secondaryColor, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColor);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColorPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColorDisabled);
- secondaryAlternativeColorTransparent = colorWithAlpha(secondaryAlternativeColor, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForeground);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForegroundHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForegroundPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForegroundDisabled);
- secondaryColorForegroundTransparent = colorWithAlpha(secondaryColorForeground, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor1);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor2);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor3);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor4);
- semiTransparentColorTransparent = colorWithAlpha(semiTransparentColor1, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccess);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccessHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccessPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccessDisabled);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfo);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfoHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfoPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfoDisabled);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarning);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarningHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarningPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarningDisabled);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorError);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorErrorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorErrorPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorErrorDisabled);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForeground);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForegroundHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForegroundPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForegroundDisabled);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColor);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColorHovered);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColorPressed);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColorDisabled);
- borderColorTransparent = colorWithAlpha(borderColor, 0);
-
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, shadowColor1);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, shadowColor2);
- TRY_GET_COLOR_ATTRIBUTE(jsonObj, shadowColor3);
- shadowColorTransparent = colorWithAlpha(shadowColor1, 0);
-
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSize);
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH1);
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH2);
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH3);
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH4);
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH5);
- TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeS1);
- TRY_GET_INT_ATTRIBUTE(jsonObj, animationDuration);
- TRY_GET_INT_ATTRIBUTE(jsonObj, focusAnimationDuration);
- TRY_GET_INT_ATTRIBUTE(jsonObj, sliderAnimationDuration);
- TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, borderRadius);
- TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, checkBoxBorderRadius);
- TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, menuItemBorderRadius);
- TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, menuBarItemBorderRadius);
- TRY_GET_INT_ATTRIBUTE(jsonObj, borderWidth);
- TRY_GET_INT_ATTRIBUTE(jsonObj, controlHeightLarge);
- TRY_GET_INT_ATTRIBUTE(jsonObj, controlHeightMedium);
- TRY_GET_INT_ATTRIBUTE(jsonObj, controlHeightSmall);
- TRY_GET_INT_ATTRIBUTE(jsonObj, controlDefaultWidth);
- TRY_GET_INT_ATTRIBUTE(jsonObj, dialMarkLength);
- TRY_GET_INT_ATTRIBUTE(jsonObj, dialMarkThickness);
- TRY_GET_INT_ATTRIBUTE(jsonObj, dialTickLength);
- TRY_GET_INT_ATTRIBUTE(jsonObj, dialTickSpacing);
- TRY_GET_INT_ATTRIBUTE(jsonObj, dialGrooveThickness);
- TRY_GET_INT_ATTRIBUTE(jsonObj, focusBorderWidth);
-
- auto iconExtent = 16;
- TRY_GET_INT_ATTRIBUTE(jsonObj, iconExtent);
- iconSize = QSize{ iconExtent, iconExtent };
- iconSizeMedium = iconSize * 1.5;
- iconSizeLarge = iconSize * 2;
- iconSizeExtraSmall = iconSize * 0.75;
-
- TRY_GET_INT_ATTRIBUTE(jsonObj, sliderTickSize);
- TRY_GET_INT_ATTRIBUTE(jsonObj, sliderTickSpacing);
- TRY_GET_INT_ATTRIBUTE(jsonObj, sliderTickThickness);
- TRY_GET_INT_ATTRIBUTE(jsonObj, sliderGrooveHeight);
- TRY_GET_INT_ATTRIBUTE(jsonObj, progressBarGrooveHeight);
- TRY_GET_INT_ATTRIBUTE(jsonObj, spacing);
- TRY_GET_INT_ATTRIBUTE(jsonObj, scrollBarThicknessFull);
- TRY_GET_INT_ATTRIBUTE(jsonObj, scrollBarThicknessSmall);
- TRY_GET_INT_ATTRIBUTE(jsonObj, scrollBarMargin);
- TRY_GET_INT_ATTRIBUTE(jsonObj, tabBarPaddingTop);
- TRY_GET_INT_ATTRIBUTE(jsonObj, tabBarTabMaxWidth);
- TRY_GET_INT_ATTRIBUTE(jsonObj, tabBarTabMinWidth);
-
- tabBarTabMaxWidth = std::max(0, tabBarTabMaxWidth);
- tabBarTabMinWidth = std::max(0, tabBarTabMinWidth);
- if (tabBarTabMinWidth > tabBarTabMaxWidth) {
- std::swap(tabBarTabMinWidth, tabBarTabMaxWidth);
- }
+bool Theme::initializeFromJson(QJsonDocument const& jsonDoc) {
+ if (!jsonDoc.isObject())
+ return false;
+
+ const auto jsonObj = jsonDoc.object();
+ if (!jsonObj.isEmpty()) {
+ // Parse metadata.
+ auto const metaObj = jsonObj.value(QStringLiteral("meta")).toObject();
+ if (!jsonObjHasAllKeys(metaObj, {
+ QStringLiteral("name"),
+ QStringLiteral("version"),
+ QStringLiteral("author"),
+ }))
+ return false;
+
+ meta = ThemeMeta{
+ tryGetString(metaObj, QStringLiteral("name"), {}),
+ tryGetString(metaObj, QStringLiteral("version"), {}),
+ tryGetString(metaObj, QStringLiteral("author"), {}),
+ };
+
+ // Parse all values.
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain1);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain2);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain3);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorMain4);
+ backgroundColorMainTransparent = colorWithAlpha(backgroundColorMain1, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorWorkspace);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, backgroundColorTabBar);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColorDisabled);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColor);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, neutralColorPressed);
+ neutralColorTransparent = colorWithAlpha(neutralColorDisabled, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, focusColor);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColor);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorDisabled);
+ primaryColorTransparent = colorWithAlpha(primaryColor, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForeground);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForegroundHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForegroundPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryColorForegroundDisabled);
+ primaryColorForegroundTransparent = colorWithAlpha(primaryColorForeground, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColor);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColorPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, primaryAlternativeColorDisabled);
+ primaryAlternativeColorTransparent = colorWithAlpha(primaryAlternativeColor, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColor);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorDisabled);
+ secondaryColorTransparent = colorWithAlpha(secondaryColor, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColor);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColorPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryAlternativeColorDisabled);
+ secondaryAlternativeColorTransparent = colorWithAlpha(secondaryAlternativeColor, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForeground);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForegroundHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForegroundPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, secondaryColorForegroundDisabled);
+ secondaryColorForegroundTransparent = colorWithAlpha(secondaryColorForeground, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor1);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor2);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor3);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, semiTransparentColor4);
+ semiTransparentColorTransparent = colorWithAlpha(semiTransparentColor1, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccess);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccessHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccessPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorSuccessDisabled);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfo);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfoHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfoPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorInfoDisabled);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarning);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarningHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarningPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorWarningDisabled);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorError);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorErrorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorErrorPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorErrorDisabled);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForeground);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForegroundHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForegroundPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, statusColorForegroundDisabled);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColor);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColorHovered);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColorPressed);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, borderColorDisabled);
+ borderColorTransparent = colorWithAlpha(borderColor, 0);
+
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, shadowColor1);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, shadowColor2);
+ TRY_GET_COLOR_ATTRIBUTE(jsonObj, shadowColor3);
+ shadowColorTransparent = colorWithAlpha(shadowColor1, 0);
+
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSize);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH1);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH2);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH3);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH4);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeH5);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, fontSizeS1);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, animationDuration);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, focusAnimationDuration);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, sliderAnimationDuration);
+ TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, borderRadius);
+ TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, checkBoxBorderRadius);
+ TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, menuItemBorderRadius);
+ TRY_GET_DOUBLE_ATTRIBUTE(jsonObj, menuBarItemBorderRadius);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, borderWidth);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, controlHeightLarge);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, controlHeightMedium);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, controlHeightSmall);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, controlDefaultWidth);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, dialMarkLength);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, dialMarkThickness);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, dialTickLength);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, dialTickSpacing);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, dialGrooveThickness);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, focusBorderWidth);
+
+ auto iconExtent = 16;
+ TRY_GET_INT_ATTRIBUTE(jsonObj, iconExtent);
+ iconSize = QSize{ iconExtent, iconExtent };
+ iconSizeMedium = iconSize * 1.5;
+ iconSizeLarge = iconSize * 2;
+ iconSizeExtraSmall = iconSize * 0.75;
+
+ TRY_GET_INT_ATTRIBUTE(jsonObj, sliderTickSize);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, sliderTickSpacing);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, sliderTickThickness);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, sliderGrooveHeight);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, progressBarGrooveHeight);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, spacing);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, scrollBarThicknessFull);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, scrollBarThicknessSmall);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, scrollBarMargin);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, tabBarPaddingTop);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, tabBarTabMaxWidth);
+ TRY_GET_INT_ATTRIBUTE(jsonObj, tabBarTabMinWidth);
+
+ tabBarTabMaxWidth = std::max(0, tabBarTabMaxWidth);
+ tabBarTabMinWidth = std::max(0, tabBarTabMinWidth);
+ if (tabBarTabMinWidth > tabBarTabMaxWidth) {
+ std::swap(tabBarTabMinWidth, tabBarTabMaxWidth);
}
}
+
+ return true;
}
QJsonDocument Theme::toJson() const {
@@ -423,6 +455,9 @@ QJsonDocument Theme::toJson() const {
SET_COLOR(jsonObj, backgroundColorMain3);
SET_COLOR(jsonObj, backgroundColorMain4);
+ SET_COLOR(jsonObj, backgroundColorWorkspace);
+ SET_COLOR(jsonObj, backgroundColorTabBar);
+
SET_COLOR(jsonObj, neutralColor);
SET_COLOR(jsonObj, neutralColorHovered);
SET_COLOR(jsonObj, neutralColorPressed);
@@ -551,6 +586,8 @@ bool Theme::operator==(const Theme& other) const {
&& backgroundColorMain3 == other.backgroundColorMain3
&& backgroundColorMain4 == other.backgroundColorMain4
+ && backgroundColorWorkspace == other.backgroundColorWorkspace
+
&& neutralColorDisabled == other.neutralColorDisabled
&& neutralColor == other.neutralColor
&& neutralColorHovered == other.neutralColorHovered
diff --git a/lib/src/style/ThemeManager.cpp b/lib/src/style/ThemeManager.cpp
index 93cb279..4e2c12b 100644
--- a/lib/src/style/ThemeManager.cpp
+++ b/lib/src/style/ThemeManager.cpp
@@ -3,6 +3,8 @@
#include
+#include
+
namespace oclero::qlementine {
ThemeManager::ThemeManager(QObject* parent)
: ThemeManager(nullptr, parent) {}
@@ -10,6 +12,9 @@ ThemeManager::ThemeManager(QObject* parent)
ThemeManager::ThemeManager(QlementineStyle* style, QObject* parent)
: QObject(parent) {
setStyle(style);
+ if (parent == nullptr) {
+ setParent(style);
+ }
}
QlementineStyle* ThemeManager::style() const {
@@ -32,11 +37,30 @@ const std::vector& ThemeManager::themes() const {
void ThemeManager::addTheme(const Theme& theme) {
_themes.emplace_back(theme);
emit themeCountChanged();
- if (_currentIndex == -1) {
+ if (_currentIndex < 0) {
setCurrentThemeIndex(0);
}
}
+void ThemeManager::loadDirectory(const QString& path) {
+ QDir dir(path);
+ if (!dir.exists())
+ return;
+
+ dir.setFilter(QDir::Filter::Files | QDir::Filter::NoDotAndDotDot);
+ dir.setSorting(QDir::SortFlag::Name | QDir::SortFlag::IgnoreCase);
+ const auto files = dir.entryInfoList();
+ for (const auto& file : files) {
+ QFileInfo fileInfo(file);
+ if (fileInfo.suffix().toLower() == QStringLiteral("json")) {
+ const auto themeOpt = Theme::fromJsonPath(file.absoluteFilePath());
+ if (themeOpt.has_value()) {
+ addTheme(themeOpt.value());
+ }
+ }
+ }
+}
+
QString ThemeManager::currentTheme() const {
if (_currentIndex > -1 && _currentIndex < themeCount()) {
return _themes.at(_currentIndex).meta.name;
@@ -54,11 +78,12 @@ int ThemeManager::currentThemeIndex() const {
}
void ThemeManager::setCurrentThemeIndex(int index) {
- index = std::max(-1, std::min(themeCount() - 1, index));
- if (index != _currentIndex) {
- _currentIndex = index;
- synchronizeThemeOnStyle();
- emit currentThemeChanged();
+ if (index > -1 && index < themeCount()) {
+ if (index != _currentIndex) {
+ _currentIndex = index;
+ synchronizeThemeOnStyle();
+ emit currentThemeChanged();
+ }
}
}
@@ -93,18 +118,18 @@ int ThemeManager::themeIndex(const QString& key) const {
return -1;
}
-QString ThemeManager::getLocalizedThemeName(const QString& baseThemeName) const {
- if (baseThemeName.toLower() == QStringLiteral("light")) {
- return tr("Light Theme");
- } else if (baseThemeName.toLower() == QStringLiteral("dark")) {
- return tr("Dark Theme");
- }
- return baseThemeName;
-}
-
void ThemeManager::synchronizeThemeOnStyle() {
- if (_style && _currentIndex != -1 && !_themes.empty() && _currentIndex < themeCount()) {
+ if (!_style)
+ return;
+
+ if (_themes.empty())
+ return;
+
+ if (_currentIndex >= 0 && _currentIndex < themeCount()) {
_style->setTheme(_themes.at(_currentIndex));
+ } else {
+ addTheme(_style->theme());
+ setCurrentThemeIndex(themeCount() - 1);
}
}
} // namespace oclero::qlementine
diff --git a/lib/src/tools/ThemeEditor.cpp b/lib/src/tools/ThemeEditor.cpp
index 130f881..d727346 100644
--- a/lib/src/tools/ThemeEditor.cpp
+++ b/lib/src/tools/ThemeEditor.cpp
@@ -220,8 +220,12 @@ struct ThemeEditor::Impl {
// Get theme from file and set it on the application.
const auto fileName =
QFileDialog::getOpenFileName(&owner, "Load JSON theme", previousPath, "JSON Files (*.json)");
- const auto theme = Theme(fileName);
- owner.setTheme(theme);
+ const auto themeOpt = Theme::fromJsonPath(fileName);
+ if (!themeOpt.has_value()) {
+ return;
+ }
+
+ owner.setTheme(themeOpt.value());
// Save path to QSettings.
settings.setValue(PREVIOUS_PATH_SETTINGS_KEY, fileName);
diff --git a/lib/src/utils/IconUtils.cpp b/lib/src/utils/IconUtils.cpp
new file mode 100644
index 0000000..cc33a6d
--- /dev/null
+++ b/lib/src/utils/IconUtils.cpp
@@ -0,0 +1,95 @@
+// SPDX-FileCopyrightText: Olivier Cléro
+// SPDX-License-Identifier: MIT
+
+#include
+
+#include
+
+#include
+#include
+
+namespace oclero::qlementine {
+IconTheme::IconTheme(const QColor& normal, const QColor& disabled, const QColor& checkedNormal, QColor checkedDisabled)
+ : normal(normal)
+ , disabled(disabled)
+ , checkedNormal(checkedNormal)
+ , checkedDisabled(checkedDisabled) {}
+
+IconTheme::IconTheme(const QColor& normal, const QColor& disabled)
+ : IconTheme(normal, disabled, normal, disabled) {}
+
+IconTheme::IconTheme(const QColor& normal)
+ : IconTheme(normal, normal, normal, normal) {}
+
+const QColor& IconTheme::color(QIcon::Mode mode, QIcon::State state) const {
+ switch (mode) {
+ case QIcon::Disabled:
+ return state == QIcon::On ? checkedDisabled : disabled;
+ case QIcon::Normal:
+ case QIcon::Active:
+ case QIcon::Selected:
+ default:
+ return state == QIcon::On ? checkedNormal : normal;
+ }
+}
+
+QIcon makeIconFromSvg(const QString& svgPath, const QSize& size) {
+ if (svgPath.isEmpty() || size.isEmpty())
+ return {};
+
+ QIcon icon;
+
+ QSvgRenderer svgRenderer(svgPath);
+ svgRenderer.setAspectRatioMode(Qt::AspectRatioMode::KeepAspectRatio);
+
+ for (const auto pxRatio : { 1., 2. }) {
+ QPixmap pixmap(size * pxRatio);
+ pixmap.fill(Qt::transparent);
+ {
+ QPainter painter(&pixmap);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ svgRenderer.render(&painter, pixmap.rect());
+ }
+ pixmap.setDevicePixelRatio(pxRatio);
+
+ for (const auto iconMode : { QIcon::Normal, QIcon::Disabled, QIcon::Active, QIcon::Selected }) {
+ for (const auto iconState : { QIcon::On, QIcon::Off }) {
+ icon.addPixmap(pixmap, iconMode, iconState);
+ }
+ }
+ }
+
+ return icon;
+}
+
+QIcon makeIconFromSvg(const QString& svgPath, const IconTheme& iconTheme, const QSize& size) {
+ if (svgPath.isEmpty() || size.isEmpty())
+ return {};
+
+ QIcon icon;
+
+ QSvgRenderer svgRenderer(svgPath);
+ svgRenderer.setAspectRatioMode(Qt::AspectRatioMode::KeepAspectRatio);
+
+ for (const auto pxRatio : { 1, 2 }) {
+ QPixmap pixmap(size * pxRatio);
+ pixmap.fill(Qt::transparent);
+ {
+ QPainter painter(&pixmap);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ svgRenderer.render(&painter, pixmap.rect());
+ }
+ pixmap.setDevicePixelRatio(static_cast(pxRatio));
+
+ for (const auto iconMode : { QIcon::Normal, QIcon::Disabled, QIcon::Active, QIcon::Selected }) {
+ for (const auto iconState : { QIcon::On, QIcon::Off }) {
+ const auto& fgColor = iconTheme.color(iconMode, iconState);
+ const auto coloredPixmap = qlementine::getColorizedPixmap(pixmap, fgColor);
+ icon.addPixmap(coloredPixmap, iconMode, iconState);
+ }
+ }
+ }
+
+ return icon;
+}
+} // namespace oclero::qlementine
diff --git a/lib/src/utils/ImageUtils.cpp b/lib/src/utils/ImageUtils.cpp
index c351e84..f2ee77e 100644
--- a/lib/src/utils/ImageUtils.cpp
+++ b/lib/src/utils/ImageUtils.cpp
@@ -165,26 +165,6 @@ QPixmap getCachedPixmap(QPixmap const& input, QColor const& color, ColorizeMode
return pixmapInCache.isNull() ? input : pixmapInCache;
}
-QIcon makeIconFromSvg(const QString& svgPath, const QSize& size) {
- if (svgPath.isEmpty())
- return {};
-
- QIcon icon;
- QSvgRenderer renderer(svgPath);
- constexpr auto ratios = std::array{ 1, 2 };
- for (const auto& ratio : ratios) {
- const auto pixmapSize = size * ratio;
- QPixmap pixmap(pixmapSize);
- pixmap.fill(Qt::transparent);
- QPainter painter(&pixmap);
- painter.setRenderHint(QPainter::Antialiasing, true);
- renderer.render(&painter, pixmap.rect());
- pixmap.setDevicePixelRatio(static_cast(ratio));
- icon.addPixmap(pixmap);
- }
- return icon;
-}
-
QPixmap makePixmapFromSvg(const QString& svgPath, const QSize& size) {
if (svgPath.isEmpty())
return {};
diff --git a/lib/src/utils/LayoutUtils.cpp b/lib/src/utils/LayoutUtils.cpp
new file mode 100644
index 0000000..ee1274d
--- /dev/null
+++ b/lib/src/utils/LayoutUtils.cpp
@@ -0,0 +1,56 @@
+#include
+
+#include
+#include
+
+namespace oclero {
+namespace qlementine {
+QMargins getLayoutMargins(const QWidget* widget) {
+ if (const auto* style = widget ? widget->style() : nullptr) {
+ const auto left = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
+ const auto top = style->pixelMetric(QStyle::PM_LayoutTopMargin);
+ const auto right = style->pixelMetric(QStyle::PM_LayoutRightMargin);
+ const auto bottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin);
+ return { left, top, right, bottom };
+ }
+ return { 0, 0, 0, 0 };
+}
+
+int getLayoutHSpacing(const QWidget* widget) {
+ if (const auto* style = widget ? widget->style() : nullptr) {
+ return style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
+ }
+ return 0;
+}
+
+int getLayoutVSpacing(const QWidget* widget) {
+ if (const auto* style = widget ? widget->style() : nullptr) {
+ return style->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
+ }
+ return 0;
+}
+
+std::tuple getVLayoutProps(const QWidget* widget) {
+ return std::tuple{ getLayoutVSpacing(widget), getLayoutMargins(widget) };
+}
+
+std::tuple getHLayoutProps(const QWidget* widget) {
+ return std::tuple{ getLayoutHSpacing(widget), getLayoutMargins(widget) };
+}
+
+std::tuple getFormLayoutProps(const QWidget* widget) {
+ return std::tuple{ getLayoutVSpacing(widget), getLayoutHSpacing(widget), getLayoutMargins(widget) };
+}
+
+void clearLayout(QLayout* layout) {
+ while (auto* item = layout->takeAt(0)) {
+ if (auto* widget = item->widget()) {
+ delete widget;
+ } else if (auto* item_layout = item->layout()) {
+ clearLayout(item_layout);
+ }
+ delete item;
+ }
+}
+} // namespace qlementine
+} // namespace oclero
diff --git a/lib/src/utils/MenuUtils.cpp b/lib/src/utils/MenuUtils.cpp
new file mode 100644
index 0000000..93dbb10
--- /dev/null
+++ b/lib/src/utils/MenuUtils.cpp
@@ -0,0 +1,69 @@
+#include
+
+#include
+#include
+#include
+#include
+
+namespace oclero::qlementine {
+class FlashActionHelper : QObject {
+public:
+ FlashActionHelper(QAction* action, QMenu* menu, const std::function& onAnimationFinished)
+ : QObject(action)
+ , _menu(menu)
+ , _action(action)
+ , _onAnimationFinished(onAnimationFinished) {
+ if (_menu && _action) {
+ _action->setProperty("qlementine_flashing", true);
+ _menu->blockSignals(true);
+ _timerId = startTimer(flashActionBlinkDuration);
+ }
+ }
+
+protected:
+ void timerEvent(QTimerEvent*) override {
+ if (_flashActionElapsedTime < flashActionDuration && _menu && _action) {
+ _flashActionElapsedTime += flashActionBlinkDuration;
+ const auto* currentActiveAction = _menu->activeAction();
+ _menu->setActiveAction(currentActiveAction == nullptr ? _action : nullptr);
+ } else {
+ killTimer(_timerId);
+ if (_menu) {
+ if (_action) {
+ _menu->setActiveAction(_action);
+ _action->setProperty("qlementine_flashing", false);
+ }
+ _menu->blockSignals(false);
+ }
+ if (_onAnimationFinished) {
+ _onAnimationFinished();
+ }
+ }
+ }
+
+private:
+ static constexpr int flashActionBlinkDuration{ 60 }; // ms
+ static constexpr int flashActionDuration{ 2 * flashActionBlinkDuration }; // ms
+ int _flashActionElapsedTime{ 0 }; // ms
+ int _timerId{ -1 };
+ QPointer _menu{ nullptr };
+ QPointer _action{ nullptr };
+ std::function _onAnimationFinished{};
+};
+
+QMenu* getTopLevelMenu(QMenu* menu) {
+ auto parent = menu;
+ while (parent != nullptr) {
+ auto parent_menu = qobject_cast(parent->parentWidget());
+ if (parent_menu != nullptr)
+ parent = parent_menu;
+ else
+ break;
+ }
+ return parent;
+}
+
+void flashAction(QAction* action, QMenu* menu, const std::function& onAnimationFinished) {
+ new FlashActionHelper(action, menu, onAnimationFinished);
+}
+} // namespace oclero::qlementine
diff --git a/lib/src/utils/WidgetUtils.cpp b/lib/src/utils/WidgetUtils.cpp
index d92f4fc..01bfa7d 100644
--- a/lib/src/utils/WidgetUtils.cpp
+++ b/lib/src/utils/WidgetUtils.cpp
@@ -61,18 +61,6 @@ void centerWidget(QWidget* widget, QWidget* host) {
}
}
-QMargins getDefaultMargins(const QStyle* style) {
- if (!style)
- return { 0, 0, 0, 0 };
-
- const auto paddingLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
- const auto paddingRight = style->pixelMetric(QStyle::PM_LayoutRightMargin);
- const auto paddingTop = style->pixelMetric(QStyle::PM_LayoutTopMargin);
- const auto paddingBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin);
- const auto contentsMargins = QMargins{ paddingLeft, paddingTop, paddingRight, paddingBottom };
- return contentsMargins;
-}
-
qreal getDpi(const QWidget* widget) {
if (widget) {
if (const auto* screen = widget->screen()) {
@@ -90,21 +78,4 @@ QWindow* getWindow(const QWidget* widget) {
}
return nullptr;
}
-
-void clearLayout(QLayout* layout) {
- if (!layout)
- return;
-
- QLayoutItem* item{};
- while ((item = layout->takeAt(0))) {
- if (item->layout()) {
- clearLayout(item->layout());
- delete item->layout();
- }
- if (item->widget()) {
- delete item->widget();
- }
- delete item;
- }
-}
} // namespace oclero::qlementine
diff --git a/lib/src/widgets/Expander.cpp b/lib/src/widgets/Expander.cpp
index f91a397..db6882b 100644
--- a/lib/src/widgets/Expander.cpp
+++ b/lib/src/widgets/Expander.cpp
@@ -7,13 +7,20 @@
#include
namespace oclero::qlementine {
-constexpr auto animationDurationFactor = 1;
+constexpr auto animationDurationFactor = 1.;
Expander::Expander(QWidget* parent)
: QWidget(parent) {
- const auto animationDuration = style()->styleHint(QStyle::SH_Widget_Animation_Duration);
- setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
setFocusPolicy(Qt::NoFocus);
+
+ if (_orientation == Qt::Vertical) {
+ setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ } else {
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ }
+
+ // Initialize animation.
+ const auto animationDuration = style()->styleHint(QStyle::SH_Widget_Animation_Duration);
_animation.setStartValue(QVariant::fromValue(0));
_animation.setEndValue(QVariant::fromValue(0));
_animation.setDuration(animationDuration * animationDurationFactor);
@@ -21,14 +28,32 @@ Expander::Expander(QWidget* parent)
QObject::connect(&_animation, &QVariantAnimation::valueChanged, this, [this]() {
updateGeometry();
});
+
+ QObject::connect(&_animation, &QVariantAnimation::finished, this, [this]() {
+ if (_expanded) {
+ emit didExpand();
+ } else {
+ emit didShrink();
+ }
+ });
}
QSize Expander::sizeHint() const {
const auto contentSizeHint = _content ? _content->sizeHint() : QSize{ 0, 0 };
- const auto w = contentSizeHint.width();
- const auto h = _animation.state() == QVariantAnimation::Running ? _animation.currentValue().toInt()
- : (_expanded ? contentSizeHint.height() : 0);
- return { w, h };
+ const auto isVertical = _orientation == Qt::Orientation::Vertical;
+ const auto currentValue = _animation.currentValue().toInt();
+
+ if (isVertical) {
+ const auto finalValue = _expanded ? contentSizeHint.height() : 0;
+ const auto w = contentSizeHint.width();
+ const auto h = _animation.state() == QVariantAnimation::Running ? currentValue : finalValue;
+ return { w, h };
+ } else {
+ const auto finalValue = _expanded ? contentSizeHint.width() : 0;
+ const auto h = contentSizeHint.height();
+ const auto w = _animation.state() == QVariantAnimation::Running ? currentValue : finalValue;
+ return { w, h };
+ }
}
bool Expander::event(QEvent* e) {
@@ -58,9 +83,11 @@ void Expander::updateContentGeometry() {
_content->ensurePolished();
const auto& availableSize = size();
const auto contentSizeHint = _content->sizeHint();
- const auto w = availableSize.width();
- const auto h = contentSizeHint.height();
- _content->setVisible(w > 0);
+ const auto isVertical = _orientation == Qt::Orientation::Vertical;
+ const auto w = isVertical ? availableSize.width() : contentSizeHint.width();
+ const auto h = isVertical ? contentSizeHint.height() : availableSize.height();
+ const auto visible = isVertical ? w > 0 : h > 0;
+ _content->setVisible(visible);
_content->setGeometry(0, 0, w, h);
}
}
@@ -76,8 +103,16 @@ void Expander::setExpanded(bool expanded) {
}
_expanded = expanded;
- const auto current = height();
- const auto target = _content && _expanded ? _content->sizeHint().height() : 0;
+ if (_expanded) {
+ emit aboutToExpand();
+ } else {
+ emit aboutToShrink();
+ }
+
+ const auto isVertical = _orientation == Qt::Orientation::Vertical;
+ const auto current = isVertical ? height() : width();
+ const auto contentSizeHint = _content->sizeHint();
+ const auto target = _content && _expanded ? (isVertical ? contentSizeHint.height() : contentSizeHint.width()) : 0;
const auto animationDuration = isVisible() ? style()->styleHint(QStyle::SH_Widget_Animation_Duration) : 0;
_animation.stop();
_animation.setDuration(animationDuration * animationDurationFactor);
@@ -89,6 +124,29 @@ void Expander::setExpanded(bool expanded) {
}
}
+void Expander::toggleExpanded() {
+ setExpanded(!expanded());
+}
+
+Qt::Orientation Expander::orientation() const {
+ return _orientation;
+}
+
+void Expander::setOrientation(Qt::Orientation orientation) {
+ if (_orientation != orientation) {
+ _orientation = orientation;
+
+ if (_orientation == Qt::Vertical) {
+ setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ } else {
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ }
+
+ updateGeometry();
+ emit orientationChanged();
+ }
+}
+
QWidget* Expander::content() const {
return _content;
}
@@ -102,7 +160,12 @@ void Expander::setContent(QWidget* content) {
_content = content;
if (_content) {
- _content->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored);
+ if (_orientation == Qt::Orientation::Vertical) {
+ _content->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored);
+ } else {
+ _content->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding);
+ }
+
_content->setParent(this);
_content->installEventFilter(this);
_content->setVisible(_expanded);
diff --git a/lib/src/widgets/NavigationBar.cpp b/lib/src/widgets/NavigationBar.cpp
index 868ed3b..51cf411 100644
--- a/lib/src/widgets/NavigationBar.cpp
+++ b/lib/src/widgets/NavigationBar.cpp
@@ -16,11 +16,11 @@ const QColor& NavigationBar::getBgColor(const Theme& theme) const {
const QColor& NavigationBar::getItemBgColor(MouseState mouse, const Theme& theme) const {
switch (mouse) {
case MouseState::Hovered:
- return theme.neutralColorHovered;
+ return theme.backgroundColorMain3;
case MouseState::Pressed:
- return theme.neutralColorPressed;
+ return theme.backgroundColorMain4;
default:
- return theme.neutralColorTransparent;
+ return theme.backgroundColorMainTransparent;
}
}
diff --git a/lib/src/widgets/SegmentedControl.cpp b/lib/src/widgets/SegmentedControl.cpp
index 0ff3272..049f706 100644
--- a/lib/src/widgets/SegmentedControl.cpp
+++ b/lib/src/widgets/SegmentedControl.cpp
@@ -15,11 +15,11 @@ const QColor& SegmentedControl::getBgColor(const Theme& theme) const {
const QColor& SegmentedControl::getItemBgColor(MouseState mouse, const Theme& theme) const {
switch (mouse) {
case MouseState::Hovered:
- return theme.semiTransparentColor2;
+ return theme.neutralColor;
case MouseState::Pressed:
- return theme.semiTransparentColor4;
+ return theme.neutralColorHovered;
default:
- return theme.semiTransparentColorTransparent;
+ return theme.neutralColorTransparent;
}
}
diff --git a/lib/src/widgets/Switch.cpp b/lib/src/widgets/Switch.cpp
index 946ee07..107f098 100644
--- a/lib/src/widgets/Switch.cpp
+++ b/lib/src/widgets/Switch.cpp
@@ -122,7 +122,9 @@ void Switch::enterEvent(QEnterEvent* e) {
void Switch::changeEvent(QEvent* e) {
QAbstractButton::changeEvent(e);
- if (e->type() == QEvent::Type::EnabledChange) {
+ const auto type = e->type();
+ if (type == QEvent::Type::EnabledChange || type == QEvent::Type::PaletteChange
+ || type == QEvent::Type::ApplicationPaletteChange) {
startAnimation();
}
}
diff --git a/sandbox/CMakeLists.txt b/sandbox/CMakeLists.txt
index f5ded43..33b28a6 100644
--- a/sandbox/CMakeLists.txt
+++ b/sandbox/CMakeLists.txt
@@ -2,8 +2,9 @@ set(SANDBOX_NAME "sandbox")
if(APPLE)
set(APP_ICON_MACOS "${CMAKE_SOURCE_DIR}/branding/icon/icon.icns")
- set_source_files_properties(${APP_ICON_MACOS} PROPERTIES
- MACOSX_PACKAGE_LOCATION "Resources"
+ set_source_files_properties(${APP_ICON_MACOS}
+ PROPERTIES
+ MACOSX_PACKAGE_LOCATION "Resources"
)
endif()
@@ -19,53 +20,29 @@ target_link_libraries(${SANDBOX_NAME} PUBLIC
oclero::qlementine
)
-install(TARGETS ${SANDBOX_NAME}
- BUNDLE DESTINATION .
+install(
+ TARGETS ${SANDBOX_NAME}
+ BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
-set_target_properties(${SANDBOX_NAME} PROPERTIES
- INTERNAL_CONSOLE OFF
- EXCLUDE_FROM_ALL OFF
- FOLDER "sandbox"
- CMAKE_AUTOMOC ON
- CMAKE_AUTORCC ON
- CMAKE_AUTOUIC ON
-
- MACOSX_BUNDLE_GUI_IDENTIFIER "oclero.qlementine.${SANDBOX_NAME}"
- MACOSX_BUNDLE_BUNDLE_NAME "Sandbox"
- MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
- MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
- MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION}
- MACOSX_BUNDLE_ICON_FILE "icon.icns"
- MACOSX_BUNDLE_COPYRIGHT ${PROJECT_COPYRIGHT}
-
- XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED OFF
- XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
- XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual"
- XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS OFF
+set_target_properties(${SANDBOX_NAME}
+ PROPERTIES
+ INTERNAL_CONSOLE OFF
+ EXCLUDE_FROM_ALL OFF
+ FOLDER "tools"
+ CMAKE_AUTOMOC ON
+ CMAKE_AUTORCC ON
+ CMAKE_AUTOUIC ON
+ MACOSX_BUNDLE_GUI_IDENTIFIER "oclero.qlementine.${SANDBOX_NAME}"
+ MACOSX_BUNDLE_BUNDLE_NAME "Sandbox"
+ MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
+ MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
+ MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION}
+ MACOSX_BUNDLE_ICON_FILE "icon.icns"
+ MACOSX_BUNDLE_COPYRIGHT ${PROJECT_COPYRIGHT}
+ XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "${XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED}"
+ XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY}"
+ XCODE_ATTRIBUTE_CODE_SIGN_STYLE "${XCODE_ATTRIBUTE_CODE_SIGN_STYLE}"
+ XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS OFF
)
-
-# target_deploy_qt(sandbox)
-
-# if(${QT_VERSION_MAJOR} STREQUAL "6")
-# # NB: Broken in 6.7.x
-
-# # qt_generate_deploy_app_script(
-# # TARGET ${SANDBOX_NAME}
-# # OUTPUT_SCRIPT SANDBOX_DEPLOY_SCRIPT
-# # )
-
-# # add_custom_command(TARGET ${SANDBOX_NAME} POST_BUILD
-# # COMMENT "Deploying Qt..."
-# # COMMAND ${CMAKE_COMMAND} -P ${SANDBOX_DEPLOY_SCRIPT} -DQT_DEPLOY_PREFIX=$ -DQT_DEPLOY_BIN_DIR=.
-# # )
-# # install(SCRIPT ${SANDBOX_DEPLOY_SCRIPT})
-
-# # add_custom_command(TARGET ${SANDBOX_NAME} POST_BUILD
-# # COMMENT "Deploying Qt..."
-# # COMMAND ${CMAKE_COMMAND} -P ${SANDBOX_DEPLOY_SCRIPT}
-# # )
-# else()
-# target_deploy_qt(sandbox)
-# endif()
diff --git a/sandbox/resources/dark.json b/sandbox/resources/dark.json
deleted file mode 100644
index 663b1bc..0000000
--- a/sandbox/resources/dark.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
- "backgroundColorMain1": "#1f1f1f",
- "backgroundColorMain2": "#2a2a2a",
- "backgroundColorMain3": "#363636",
- "backgroundColorMain4": "#414141",
- "borderColor": "#4b4b4b",
- "borderColorDisabled": "#444444",
- "borderColorHovered": "#717171",
- "borderColorPressed": "#a0a0a0",
- "focusColor": "#34988666",
- "meta": {
- "author": "Olivier Cléro",
- "name": "Dark",
- "version": "1.4.0"
- },
- "neutralColor": "#565656",
- "neutralColorDisabled": "#353535",
- "neutralColorHovered": "#4c4c4c",
- "neutralColorPressed": "#404040",
- "primaryAlternativeColor": "#126d5d",
- "primaryAlternativeColorDisabled": "#1C3B36",
- "primaryAlternativeColorHovered": "#0d6354",
- "primaryAlternativeColorPressed": "#177665",
- "primaryColor": "#349886",
- "primaryColorDisabled": "#2c403c",
- "primaryColorForeground": "#ffffff",
- "primaryColorForegroundDisabled": "#3a534d",
- "primaryColorForegroundHovered": "#ffffff",
- "primaryColorForegroundPressed": "#ffffff",
- "primaryColorHovered": "#2a7a6b",
- "primaryColorPressed": "#2f8979",
- "secondaryAlternativeColor": "#8f8f8f",
- "secondaryAlternativeColorDisabled": "#8f8f8f3f",
- "secondaryAlternativeColorHovered": "#797979",
- "secondaryAlternativeColorPressed": "#848484",
- "secondaryColor": "#ffffff",
- "secondaryColorDisabled": "#ffffff33",
- "secondaryColorForeground": "#2f2f2f",
- "secondaryColorForegroundDisabled": "#2f2f2f",
- "secondaryColorForegroundHovered": "#2f2f2f",
- "secondaryColorForegroundPressed": "#2f2f2f",
- "secondaryColorHovered": "#d5d5d5",
- "secondaryColorPressed": "#ebebeb",
- "semiTransparentColor1": "0xffffff18",
- "semiTransparentColor2": "0xffffff23",
- "semiTransparentColor3": "0xffffff28",
- "semiTransparentColor4": "0xffffff2d",
- "shadowColor1": "#00000066",
- "shadowColor2": "#000000bb",
- "shadowColor3": "#000000ff",
- "statusColorError": "#ef5151",
- "statusColorErrorDisabled": "#ef515133",
- "statusColorErrorHovered": "#c64848",
- "statusColorErrorPressed": "#da4d4d",
- "statusColorForeground": "#ffffff",
- "statusColorForegroundDisabled": "#ffffff26",
- "statusColorForegroundHovered": "#ffffff",
- "statusColorForegroundPressed": "#ffffff",
- "statusColorInfo": "#4ab9e9",
- "statusColorInfoDisabled": "#4ab9e933",
- "statusColorInfoHovered": "#429bc1",
- "statusColorInfoPressed": "#46aad6",
- "statusColorSuccess": "#32cd79",
- "statusColorSuccessDisabled": "#32cd7a26",
- "statusColorSuccessHovered": "#2eaa68",
- "statusColorSuccessPressed": "#30bc71",
- "statusColorWarning": "#ffcd1e",
- "statusColorWarningDisabled": "#ffcd1e33",
- "statusColorWarningHovered": "#d2ab1f",
- "statusColorWarningPressed": "#e9bc1f"
-}
diff --git a/sandbox/resources/plus_24.svg b/sandbox/resources/plus_24.svg
deleted file mode 100644
index 1bcc698..0000000
--- a/sandbox/resources/plus_24.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/sandbox/resources/refresh.svg b/sandbox/resources/refresh.svg
deleted file mode 100644
index 2f29c6b..0000000
--- a/sandbox/resources/refresh.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/sandbox/resources/sandbox.qrc b/sandbox/resources/sandbox.qrc
index 3d148a6..df575d2 100644
--- a/sandbox/resources/sandbox.qrc
+++ b/sandbox/resources/sandbox.qrc
@@ -1,14 +1,12 @@
-
- dark.json
- light.json
+
+ themes/dark.json
+ themes/light.json
qlementine_icon.ico
qlementine_icon.icns
- refresh.svg
- plus_24.svg
test_image_16x16.png
- scene_light.svg
- scene_material.svg
- scene_object.svg
+ test_image_16x16.svg
+ test_image_24x24.svg
+ test_image_color_16x16.svg
diff --git a/sandbox/resources/scene_light.svg b/sandbox/resources/scene_light.svg
deleted file mode 100644
index 5e46abf..0000000
--- a/sandbox/resources/scene_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/sandbox/resources/scene_material.svg b/sandbox/resources/scene_material.svg
deleted file mode 100644
index e46b631..0000000
--- a/sandbox/resources/scene_material.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/sandbox/resources/scene_object.svg b/sandbox/resources/scene_object.svg
deleted file mode 100644
index 57bf3b9..0000000
--- a/sandbox/resources/scene_object.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/sandbox/resources/test_image_16x16.svg b/sandbox/resources/test_image_16x16.svg
new file mode 100644
index 0000000..b4b1bb2
--- /dev/null
+++ b/sandbox/resources/test_image_16x16.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sandbox/resources/test_image_24x24.svg b/sandbox/resources/test_image_24x24.svg
new file mode 100644
index 0000000..e306dad
--- /dev/null
+++ b/sandbox/resources/test_image_24x24.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sandbox/resources/test_image_color_16x16.svg b/sandbox/resources/test_image_color_16x16.svg
new file mode 100644
index 0000000..c8e978e
--- /dev/null
+++ b/sandbox/resources/test_image_color_16x16.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sandbox/resources/themes b/sandbox/resources/themes
new file mode 120000
index 0000000..afbec12
--- /dev/null
+++ b/sandbox/resources/themes
@@ -0,0 +1 @@
+../../showcase/resources/themes
\ No newline at end of file
diff --git a/sandbox/src/CsdWindow.cpp b/sandbox/src/CsdWindow.cpp
deleted file mode 100644
index f395bc8..0000000
--- a/sandbox/src/CsdWindow.cpp
+++ /dev/null
@@ -1,766 +0,0 @@
-// SPDX-FileCopyrightText: Olivier Cléro
-// SPDX-License-Identifier: MIT
-
-#include "CsdWindow.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-CsdWindow::CsdWindow(QWidget* parent)
- : oclero::qlementine::FramelessWindow(parent) {
- setWindowIcon(QIcon(":/qlementine_icon.ico"));
- setupUi();
- resize(600, 400);
- setWindowTitle("Custom native window");
- populateMenuBar(menuBar());
-}
-
-void CsdWindow::paintEvent(QPaintEvent* event) {
- if (_useDefaultColor) {
- FramelessWindow::paintEvent(event);
- } else {
- QPainter painter(this);
- painter.fillRect(rect(), _backgroundColor);
- }
-}
-
-void CsdWindow::setupUi() {
- auto* content = new QTabWidget(this);
-
- auto addseg = [](const QString& caption, QWidget* parent) {
- auto* segTitle = new QWidget(parent);
- auto layout = new QHBoxLayout;
- segTitle->setLayout(layout);
- layout->addWidget(new QLabel(caption, segTitle));
- auto* hline = new QFrame(segTitle);
- hline->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
- hline->setFrameShape(QFrame::HLine);
- layout->addWidget(hline);
- return segTitle;
- };
-
- // Basic Widget
- {
- auto* page = new QScrollArea(content);
-
- auto* root = new QWidget(page);
- page->setWidget(root);
- page->setWidgetResizable(true);
- page->setAlignment(Qt::AlignHCenter);
- auto* layout = new QVBoxLayout();
- root->setLayout(layout);
-
- // QCheckBox
- {
- auto* widget = new QWidget(root);
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- auto* checkbox = new QCheckBox("Normal", widget);
- llayout->addWidget(checkbox);
-
- auto* checkbox2 = new QCheckBox("Disabled", widget);
- checkbox2->setDisabled(true);
- llayout->addWidget(checkbox2);
-
- auto* checkbox3 = new QCheckBox("WithIcon", widget);
- checkbox3->setIcon(QIcon(QStringLiteral(":/plus_24.svg")));
- llayout->addWidget(checkbox3);
-
- auto* checkbox4 = new QCheckBox("NoneCheckable", widget);
- checkbox4->setCheckable(false);
- llayout->addWidget(checkbox4);
-
- auto* checkbox5 = new QCheckBox("PartiallyChecked", widget);
- checkbox5->setCheckState(Qt::CheckState::PartiallyChecked);
- llayout->addWidget(checkbox5);
-
- layout->addWidget(addseg("QCheckBox", root));
- layout->addWidget(widget);
- }
-
- // QComboBox
- {
- auto createCombo = [](QWidget* parent) {
- auto* widget = new QComboBox(parent);
- widget->addItems({ "North", "South", "West", "East" });
- widget->addItem(QIcon(QStringLiteral(":/plus_24.svg")), "Directions");
- return widget;
- };
-
- auto* widget = new QWidget(root);
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- auto* normal = createCombo(widget);
- llayout->addWidget(normal);
- auto* editable = createCombo(widget);
- editable->setEditable(true);
- llayout->addWidget(editable);
-
- layout->addWidget(addseg("QComboBox", root));
- layout->addWidget(widget);
- }
-
- // QCommandLinkButton
- {
- auto* widget = new QCommandLinkButton("ClickMe", "A vista style button", root);
-
- layout->addWidget(addseg("QCommandLinkButton", root));
- layout->addWidget(widget);
- }
-
- // QDateEdit
- {
- auto* widget = new QDateEdit(root);
-
- layout->addWidget(addseg("QDateEdit", root));
- layout->addWidget(widget);
- }
-
- // QDateTimeEdit
- {
- auto* widget = new QDateTimeEdit(root);
-
- layout->addWidget(addseg("QDateTimeEdit", root));
- layout->addWidget(widget);
- }
-
- // QDial
- {
- auto* widget = new QDial(root);
-
- layout->addWidget(addseg("QDial", root));
- layout->addWidget(widget);
- }
-
- // QDoubleSpinBox
- {
- auto* widget = new QDoubleSpinBox(root);
-
- layout->addWidget(addseg("QDoubleSpinBox", root));
- layout->addWidget(widget);
- }
-
- // QFontComboBox
- {
- auto* widget = new QFontComboBox(root);
-
- layout->addWidget(addseg("QFontComboBox", root));
- layout->addWidget(widget);
- }
-
- // QLCDNumber
- {
- auto* widget = new QLCDNumber(root);
- widget->setDigitCount(1000);
- widget->setMaximumWidth(200);
-
- layout->addWidget(addseg("QLCDNumber", root));
- layout->addWidget(widget);
- }
-
- // QLabel
- {
- auto* widget = new QLabel(root);
- widget->setText("This is a label");
-
- layout->addWidget(addseg("QLabel", root));
- layout->addWidget(widget);
- }
-
- // QLineEdit
- {
- auto* widget = new QWidget(root);
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- auto* normal = new QLineEdit(widget);
- llayout->addWidget(normal);
- auto* placeHold = new QLineEdit(widget);
- placeHold->setPlaceholderText("typing...");
- llayout->addWidget(placeHold);
-
- layout->addWidget(addseg("QLineEdit", root));
- layout->addWidget(widget);
- }
-
- // QMenu
- {
- auto* widget = new QMenuBar(root);
- auto* menu = new QMenu("Menu", widget);
- menu->addAction(QIcon(QStringLiteral(":/plus_24.svg")), "Item1");
-
-
- layout->addWidget(addseg("QMenu", root));
- layout->addWidget(widget);
- }
-
- // QProgressBar
- {
- auto* widget = new QProgressBar(root);
- widget->setValue(42);
-
- layout->addWidget(addseg("QProgressBar", root));
- layout->addWidget(widget);
- }
-
- // QPushButton
- {
- auto* widget = new QWidget(root);
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- auto* pushButton = new QPushButton("Normal", widget);
- llayout->addWidget(pushButton);
-
- auto* pushButton2 = new QPushButton("Disabled", widget);
- pushButton2->setDisabled(true);
- llayout->addWidget(pushButton2);
-
- auto* pushButton3 = new QPushButton("WithIcon", widget);
- pushButton3->setIcon(QIcon(QStringLiteral(":/plus_24.svg")));
- llayout->addWidget(pushButton3);
-
- auto* pushButton4 = new QPushButton("Flat", widget);
- pushButton4->setFlat(true);
- llayout->addWidget(pushButton4);
-
- layout->addWidget(addseg("QPushButton", root));
- layout->addWidget(widget);
- }
-
- // QRadioButton
- {
- auto* widget = new QWidget(root);
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- auto* radiobutton = new QRadioButton("Normal", widget);
- llayout->addWidget(radiobutton);
-
- auto* radiobutton2 = new QRadioButton("Disabled", widget);
- radiobutton2->setDisabled(true);
- llayout->addWidget(radiobutton2);
-
- auto* radiobutton3 = new QRadioButton("WithIcon", widget);
- radiobutton3->setIcon(QIcon(QStringLiteral(":/plus_24.svg")));
- llayout->addWidget(radiobutton3);
-
- auto* radiobutton4 = new QRadioButton("NoneCheckable", widget);
- radiobutton4->setCheckable(false);
- llayout->addWidget(radiobutton4);
-
- layout->addWidget(addseg("QRadioButton", root));
- layout->addWidget(widget);
- }
-
- // QScrollBar
- {
- auto* widget = new QScrollBar(Qt::Horizontal, root);
-
- layout->addWidget(addseg("QScrollBar", root));
- layout->addWidget(widget);
- }
-
- // QSlider
- {
- auto* widget = new QSlider(Qt::Horizontal, root);
- auto* widget2 = new QSlider(Qt::Vertical, root);
-
- layout->addWidget(addseg("QSlider", root));
- layout->addWidget(widget);
- layout->addWidget(widget2);
- }
-
- // QSpinBox
- {
- auto* widget = new QSpinBox(root);
-
- layout->addWidget(addseg("QSpinBox", root));
- layout->addWidget(widget);
- }
-
- // QTabBar
- {
- auto* widget = new QTabBar(root);
- widget->addTab("Page1");
- widget->addTab(QIcon(QStringLiteral(":/plus_24.svg")), "Page2");
- widget->addTab(QIcon(QStringLiteral(":/plus_24.svg")), "Page3");
- widget->setExpanding(false);
-
- layout->addWidget(addseg("QTabBar", root));
- layout->addWidget(widget);
- }
-
- // QTimeEdit
- {
- auto* widget = new QTimeEdit(root);
-
- layout->addWidget(addseg("QTimeEdit", root));
- layout->addWidget(widget);
- }
-
- // QToolBox
- {
- auto* widget = new QToolBox(root);
- widget->addItem(new QWidget(), QIcon(QStringLiteral(":/plus_24.svg")), "Item1");
- widget->addItem(new QWidget(), QIcon(QStringLiteral(":/plus_24.svg")), "Item2");
-
- layout->addWidget(addseg("QToolBox", root));
- layout->addWidget(widget);
- }
-
- // QToolButton
- {
- auto* widget = new QToolBar(root);
-
- auto icon = QIcon(QStringLiteral(":/plus_24.svg"));
-
- auto* toolbutton1 = new QToolButton(widget);
- toolbutton1->setIcon(icon);
- toolbutton1->setToolTip("with actions");
- toolbutton1->addAction(new QAction(icon, "Item1"));
- toolbutton1->addAction(new QAction(icon, "Item2"));
- widget->addWidget(toolbutton1);
-
- auto* toolbutton2 = new QToolButton(widget);
- toolbutton2->setIcon(icon);
- toolbutton2->setToolTip("QToolButton::DelayedPopup");
- toolbutton2->setPopupMode(QToolButton::DelayedPopup);
- widget->addWidget(toolbutton2);
-
- auto* toolbutton3 = new QToolButton(widget);
- toolbutton3->setIcon(icon);
- toolbutton3->setToolTip("QToolButton::MenuButtonPopup");
- toolbutton3->setPopupMode(QToolButton::MenuButtonPopup);
- widget->addWidget(toolbutton3);
-
- auto* toolbutton4 = new QToolButton(widget);
- toolbutton4->setIcon(icon);
- toolbutton4->setToolTip("QToolButton::InstantPopup");
- toolbutton4->setPopupMode(QToolButton::InstantPopup);
- widget->addWidget(toolbutton4);
-
- auto* toolbutton5 = new QToolButton(widget);
- toolbutton5->setIcon(icon);
- toolbutton5->setToolTip("Qt::UpArrow");
- toolbutton5->setArrowType(Qt::UpArrow);
- widget->addWidget(toolbutton5);
-
- auto* toolbutton6 = new QToolButton(widget);
- toolbutton6->setIcon(icon);
- toolbutton6->setToolTip("Qt::DownArrow");
- toolbutton6->setArrowType(Qt::DownArrow);
- widget->addWidget(toolbutton6);
-
- auto* toolbutton7 = new QToolButton(widget);
- toolbutton7->setIcon(icon);
- toolbutton7->setToolTip("Qt::LeftArrow");
- toolbutton7->setArrowType(Qt::LeftArrow);
- widget->addWidget(toolbutton7);
-
- auto* toolbutton8 = new QToolButton(widget);
- toolbutton8->setIcon(icon);
- toolbutton8->setToolTip("Qt::RightArrow");
- toolbutton8->setArrowType(Qt::RightArrow);
- widget->addWidget(toolbutton8);
-
- auto* toolbutton9 = new QToolButton(widget);
- toolbutton9->setIcon(icon);
- toolbutton9->setToolTip("AutoRaise enabled");
- toolbutton9->setAutoRaise(true);
- widget->addWidget(toolbutton9);
-
- layout->addWidget(addseg("QToolBox", root));
- layout->addWidget(widget);
- }
-
- layout->addStretch();
- content->addTab(page, "Basic Widget");
- }
-
-
- // Advanced Widget
- {
- auto* page = new QScrollArea(content);
-
- auto* root = new QWidget(page);
- page->setWidget(root);
- page->setWidgetResizable(true);
- page->setAlignment(Qt::AlignHCenter);
- auto* layout = new QVBoxLayout();
- root->setLayout(layout);
-
- // QCalendarWidget
- {
- auto* calendar = new QCalendarWidget(root);
- layout->addWidget(addseg("QCalendarWidget", root));
- layout->addWidget(calendar);
- }
-
- // QDialogButtonBox
- {
- auto* widget = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, root);
- layout->addWidget(addseg("QDialogButtonBox", root));
- layout->addWidget(widget);
- }
-
-
- // QDialog
- {
- auto* widget = new QWidget(root);
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- auto* colorDialog = new QPushButton("QColorDialog", root);
- connect(colorDialog, &QPushButton::clicked, []() {
- QColorDialog dialog;
- dialog.exec();
- });
- llayout->addWidget(colorDialog);
-
- auto* fileDialog = new QPushButton("QFileDialog", root);
- connect(fileDialog, &QPushButton::clicked, []() {
- QFileDialog dialog;
- dialog.exec();
- });
- llayout->addWidget(fileDialog);
-
- auto* fontDialog = new QPushButton("QFontDialog", root);
- connect(fontDialog, &QPushButton::clicked, []() {
- QFontDialog dialog;
- dialog.exec();
- });
- llayout->addWidget(fontDialog);
-
- layout->addWidget(addseg("QDialog(s)", root));
- layout->addWidget(widget);
- }
-
- layout->addStretch();
- content->addTab(page, "Advanced Widget");
- }
-
- // Organizer Widget
- {
- auto* page = new QScrollArea(content);
-
- auto* root = new QWidget(page);
- page->setWidget(root);
- page->setWidgetResizable(true);
- page->setAlignment(Qt::AlignHCenter);
- auto* layout = new QVBoxLayout();
- root->setLayout(layout);
-
- // QButtonGroup
- {
- auto* buttonGroup = new QButtonGroup(root);
- buttonGroup->addButton(new QCheckBox("Button1"));
- buttonGroup->addButton(new QCheckBox("Button2"));
- buttonGroup->setExclusive(true);
- layout->addWidget(addseg("QButtonGroup(checkbox list)", root));
- for (auto* button : buttonGroup->buttons()) {
- layout->addWidget(button);
- }
- }
-
- // QGroupBox
- {
- auto* widget = new QGroupBox("Group", root);
-
- auto* llayout = new QHBoxLayout;
- widget->setLayout(llayout);
-
- llayout->addWidget(new QPushButton("Button1", widget));
- llayout->addWidget(new QPushButton("Button2", widget));
-
- layout->addWidget(addseg("QGroupBox", root));
- layout->addWidget(widget);
- }
-
- // QSplitter
- {
- auto* widget = new QSplitter(root);
-
- layout->addWidget(addseg("QSplitter", root));
- layout->addWidget(widget);
- }
-
- // QTabWidget
- {
- auto* widget = new QTabWidget(root);
- widget->addTab(new QLabel("Page1"), "Page1");
- widget->addTab(new QLabel("Page2"), QIcon(QStringLiteral(":/plus_24.svg")), "Page2");
- widget->addTab(new QLabel("Page3"), QIcon(QStringLiteral(":/plus_24.svg")), "Page3");
-
- layout->addWidget(addseg("QTabWidget", root));
- layout->addWidget(widget);
- }
-
- layout->addStretch();
- content->addTab(page, "Organizer Widget");
- }
-
- // Model/View
- {
- auto* page = new QScrollArea(content);
-
- auto* root = new QWidget(page);
- page->setWidget(root);
- page->setWidgetResizable(true);
- page->setAlignment(Qt::AlignHCenter);
- auto* layout = new QVBoxLayout();
- root->setLayout(layout);
-
- // QListWidget
- {
- auto* widget = new QListWidget(root);
-
- widget->addItems({ "Item1", "Item2", "Item3" });
-
- layout->addWidget(addseg("QListWidget", root));
- layout->addWidget(widget);
- }
-
- // QTableWidget
- {
- auto* widget = new QTableWidget(root);
-
- constexpr int rows = 100;
- constexpr int cols = 10;
-
- widget->setColumnCount(cols);
- widget->setRowCount(rows);
-
- for (int row = 0; row < rows; row++) {
- for (int col = 0; col < cols; col++) {
- widget->setItem(row, col, new QTableWidgetItem(QString::number(row) + ":" + QString::number(col)));
- }
- }
-
- layout->addWidget(addseg("QTableWidget", root));
- layout->addWidget(widget);
- }
-
- // QTreeView
- {
- auto* widget = new QTreeView(root);
- auto* model = new QFileSystemModel(root);
- widget->setModel(model);
- model->setRootPath(QCoreApplication::applicationDirPath());
-
- layout->addWidget(addseg("QTreeView", root));
- layout->addWidget(widget);
- }
-
-
- layout->addStretch();
- content->addTab(page, "Model/View");
- }
-
- // Main Window
- {
- auto* page = new QScrollArea(content);
-
- auto* root = new QWidget(page);
- page->setWidget(root);
- page->setWidgetResizable(true);
- page->setAlignment(Qt::AlignHCenter);
- auto* layout = new QVBoxLayout();
- root->setLayout(layout);
-
-
- // MainWindow
- {
- auto icon = QIcon(QStringLiteral(":/plus_24.svg"));
-
- auto* widget = new QMainWindow();
- auto* focus = new QFocusFrame();
- focus->setWidget(widget);
-
- auto* status = new QStatusBar(widget);
- status->addWidget(new QLabel("Status1"));
- status->addWidget(new QLabel("Status2"), 100);
- status->addWidget(new QLabel("Status3"));
- widget->setStatusBar(status);
-
- auto* menu = new QMenuBar(widget);
- auto* fmenu = menu->addMenu("File");
- auto* emenu = menu->addMenu("Edit");
- auto* vmenu = menu->addMenu("View");
- menu->addMenu("Window");
- auto* hmenu = menu->addMenu("Help");
- widget->setMenuBar(menu);
- auto* anew = fmenu->addAction(icon, "New");
- auto* aopen = fmenu->addAction(icon, "Open");
- auto* asave = fmenu->addAction(icon, "Save");
- auto* asaveas = fmenu->addAction(icon, "Save As");
- fmenu->addSeparator();
- auto* aclose = fmenu->addAction(icon, "Close");
- auto* acopy = emenu->addAction(icon, "Copy");
- auto* acut = emenu->addAction(icon, "Cut");
- auto* apaste = emenu->addAction(icon, "Paste");
- auto* ahelp = hmenu->addAction(icon, "Help");
- auto* aabout = hmenu->addAction(icon, "About");
-
- auto* ftoolbar = widget->addToolBar("File");
- ftoolbar->setAllowedAreas(Qt::ToolBarArea::AllToolBarAreas);
- ftoolbar->setMovable(true);
- ftoolbar->setIconSize({ 24, 24 });
- ftoolbar->addActions({ anew, aopen, asave, asaveas, aclose });
-
- auto* etoolbar = widget->addToolBar("Edit");
- etoolbar->setAllowedAreas(Qt::ToolBarArea::AllToolBarAreas);
- etoolbar->setMovable(true);
- etoolbar->setIconSize({ 24, 24 });
- etoolbar->addActions({ acopy, acut, apaste });
-
- auto* htoolbar = widget->addToolBar("Help");
- htoolbar->setAllowedAreas(Qt::ToolBarArea::AllToolBarAreas);
- htoolbar->setMovable(true);
- htoolbar->setIconSize({ 24, 24 });
- htoolbar->addActions({ ahelp, aabout });
-
- widget->setDockOptions(QMainWindow::DockOption::AllowTabbedDocks);
- auto* treeview = new QTreeView();
- auto* treemodel = new QFileSystemModel();
- treeview->setModel(treemodel);
- treemodel->setRootPath(QCoreApplication::applicationDirPath());
- auto* dock1 = new QDockWidget("Browser", widget);
- dock1->setWidget(treeview);
- dock1->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
- vmenu->addAction(dock1->toggleViewAction());
- widget->addDockWidget(Qt::RightDockWidgetArea, dock1);
-
- auto* docs = new QListWidget();
- docs->addItems(
- QStringList()
- << "A custom QStyle named QlementineStyle, that implements all the necessary API to give a modern look and feel to your Qt application. It's a drop-in replacement for the default QStyle."
- << "An actual way to have client-side decoration (CSD) on your Qt window, with actual OS window animations and effects. (Windows only, at the moment)"
- << "Lots of utilities to help you write beautiful Qt widgets.");
- docs->setWordWrap(true);
- auto* dock2 = new QDockWidget(tr("Features"), this);
- dock2->setWidget(docs);
- dock2->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
- vmenu->addAction(dock2->toggleViewAction());
- widget->addDockWidget(Qt::RightDockWidgetArea, dock2);
-
-
- auto* mdiarea = new QMdiArea(widget);
- widget->setCentralWidget(mdiarea);
-
- auto* subwnd1 = mdiarea->addSubWindow(new QTextEdit(), Qt::Window);
- subwnd1->setWindowTitle("Window1");
- auto* subwnd2 = mdiarea->addSubWindow(new QTextEdit(), Qt::Window);
- subwnd2->setWindowTitle("Window2");
-
- layout->addWidget(widget);
- }
-
- layout->addStretch();
- content->addTab(page, "Main Window ");
- }
-
- // window color
- {
- auto* page = new QWidget(content);
- auto* verticalLayout = new QVBoxLayout(page);
-
- // Slider to modify the window background color.
- auto* slider = new QSlider(page);
- slider->setRange(0, 255);
- slider->setValue(_backgroundColor.red());
- QObject::connect(slider, &QSlider::valueChanged, this, [this](int value) {
- _backgroundColor.setRed(value);
- if (!_useDefaultColor) {
- update();
- }
- });
- slider->setMinimumWidth(255);
- slider->setMaximumWidth(350);
- verticalLayout->addWidget(slider, 0, Qt::AlignmentFlag::AlignCenter);
-
- // Checkbox to use or not the default background color.
- auto* checkbox = new QCheckBox("Use default window color", page);
- checkbox->setChecked(false);
-
- QObject::connect(checkbox, &QCheckBox::toggled, this, [this](bool checked) {
- _useDefaultColor = checked;
- update();
- });
- verticalLayout->addWidget(checkbox, 0, Qt::AlignmentFlag::AlignCenter);
-
- content->addTab(page, QStringLiteral("WindowColor"));
- }
-
- setContentWidget(content);
-}
-
-void CsdWindow::populateMenuBar(QMenuBar* menuBar) {
- auto* fileMenu = new QMenu("&File", menuBar);
- {
- auto* quitAction = new QAction("&Quit", fileMenu);
- quitAction->setMenuRole(QAction::MenuRole::QuitRole);
- quitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
- quitAction->setShortcutContext(Qt::ShortcutContext::ApplicationShortcut);
- QObject::connect(quitAction, &QAction::triggered, this, []() {
- QApplication::quit();
- });
- fileMenu->addAction(quitAction);
- }
-
- menuBar->addMenu(fileMenu);
-
- auto* windowMenu = new QMenu("&Window", menuBar);
- {
- auto minimizeAction = new QAction("Minimize", windowMenu);
- QObject::connect(minimizeAction, &QAction::triggered, this, [this]() {
- windowHandle()->showMinimized();
- });
- windowMenu->addAction(minimizeAction);
-
- auto* maximizeAction = new QAction("Maximize", windowMenu);
- QObject::connect(maximizeAction, &QAction::triggered, this, [this]() {
- if (auto window = this->window()) {
- if (window->windowState() & Qt::WindowMaximized) {
- window->showNormal();
- } else {
- window->showMaximized();
- }
- }
- });
- windowMenu->addAction(maximizeAction);
-
- auto* closeAction = new QAction("&Close", windowMenu);
- QObject::connect(closeAction, &QAction::triggered, this, [this]() {
- windowHandle()->close();
- });
- windowMenu->addAction(closeAction);
- }
- menuBar->addMenu(windowMenu);
-
- auto* helpMenu = new QMenu("&Help", menuBar);
- {
- auto* aboutAction = new QAction("&About", windowMenu);
- aboutAction->setMenuRole(QAction::AboutRole);
- QObject::connect(aboutAction, &QAction::triggered, this, [this]() {
- QMessageBox msgBox(
- QMessageBox::Icon::Information, "About", "Example of frameless window", QMessageBox::NoButton, this);
- msgBox.exec();
- });
- helpMenu->addAction(aboutAction);
- }
- menuBar->addMenu(helpMenu);
-}
diff --git a/sandbox/src/CsdWindow.hpp b/sandbox/src/CsdWindow.hpp
deleted file mode 100644
index 81bc455..0000000
--- a/sandbox/src/CsdWindow.hpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-FileCopyrightText: Olivier Cléro
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include
-
-class CsdWindow : public oclero::qlementine::FramelessWindow {
- Q_OBJECT
-
-public:
- explicit CsdWindow(QWidget* parent = nullptr);
- ~CsdWindow() = default;
-
-protected:
- void paintEvent(QPaintEvent* event) override;
-
-private:
- void populateMenuBar(QMenuBar* menuBar);
- void setupUi();
-
-private:
- QColor _backgroundColor{ 255, 192, 0 };
- bool _useDefaultColor{ false };
-};
diff --git a/sandbox/src/SandboxWindow.cpp b/sandbox/src/SandboxWindow.cpp
index efd0b11..05609d0 100644
--- a/sandbox/src/SandboxWindow.cpp
+++ b/sandbox/src/SandboxWindow.cpp
@@ -4,10 +4,13 @@
#include "SandboxWindow.hpp"
#include
+#include
#include
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -50,10 +53,24 @@
#include
#include
#include
+#include
#include
namespace oclero::qlementine::sandbox {
+
+static QIcon getTestQIcon(const QSize& size = { 16, 16 }, bool colored = false) {
+ if (size.height() == 24) {
+ return QIcon(":/sandbox/test_image_24x24.svg");
+ } else {
+ return colored ? QIcon(":/sandbox/test_image_color_16x16.svg") : QIcon(":/sandbox/test_image_16x16.svg");
+ }
+}
+
+// static QIcon makeQIcon(Icons16 id, const QSize& size = { 16, 16 }) {
+// return oclero::qlementine::makeThemedIcon(id, size);
+// }
+
class ContextMenuEventFilter : public QObject {
private:
std::function _cb;
@@ -206,8 +223,17 @@ class CustomBgWidget : public QWidget {
};
struct SandboxWindow::Impl {
- Impl(SandboxWindow& o)
- : owner(o) {}
+ SandboxWindow& owner;
+ QPointer themeManager{ nullptr };
+
+ QWidget* windowContent{ nullptr };
+ QBoxLayout* windowContentLayout{ nullptr };
+ QScrollArea* globalScrollArea{ nullptr };
+ QToolBar* toolbar{ nullptr };
+
+ Impl(SandboxWindow& o, ThemeManager* themeManager)
+ : owner(o)
+ , themeManager(themeManager) {}
void beginSetupUI() {
// Create a scrollarea to wrap everything §the window can be quite huge).
@@ -243,20 +269,16 @@ struct SandboxWindow::Impl {
}
});
- auto* themeShortcut = new QShortcut(Qt::CTRL | Qt::Key_T, &owner);
- themeShortcut->setAutoRepeat(false);
- themeShortcut->setContext(Qt::ShortcutContext::ApplicationShortcut);
- QObject::connect(themeShortcut, &QShortcut::activated, themeShortcut, [this]() {
- const auto light = QStringLiteral(":/light.json");
- const auto dark = QStringLiteral(":/dark.json");
- if (lastJsonThemePath == dark) {
- lastJsonThemePath = light;
- qlementineStyle->setThemeJsonPath(light);
- } else {
- lastJsonThemePath = dark;
- qlementineStyle->setThemeJsonPath(dark);
- }
- });
+ if (themeManager) {
+ auto* themeShortcut = new QShortcut(Qt::CTRL | Qt::Key_T, &owner);
+ themeShortcut->setAutoRepeat(false);
+ themeShortcut->setContext(Qt::ShortcutContext::ApplicationShortcut);
+ QObject::connect(themeShortcut, &QShortcut::activated, themeShortcut, [this]() {
+ if (themeManager) {
+ themeManager->setNextTheme();
+ }
+ });
+ }
auto* focusShortcut = new QShortcut(Qt::CTRL | Qt::Key_F, &owner);
focusShortcut->setAutoRepeat(false);
@@ -336,7 +358,7 @@ struct SandboxWindow::Impl {
void setupUI_button() {
auto* button = new QPushButton(windowContent);
button->setText("Button with a very long text that can be elided");
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
button->setDefault(true);
windowContentLayout->addWidget(button);
@@ -354,7 +376,7 @@ struct SandboxWindow::Impl {
{
// Icon, fixed size
auto* button = new QPushButton(windowContent);
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
windowContentLayout->addWidget(button);
}
@@ -362,7 +384,7 @@ struct SandboxWindow::Impl {
// Text+Icon, fixed size
auto* button = new QPushButton(windowContent);
button->setText("Button");
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
windowContentLayout->addWidget(button);
}
@@ -370,7 +392,7 @@ struct SandboxWindow::Impl {
// Text+Icon+Menu, fixed size
auto* button = new QPushButton(windowContent);
button->setText("Button");
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
auto* menu = new QMenu(button);
@@ -390,21 +412,21 @@ struct SandboxWindow::Impl {
{
// Icon, expanding size.
auto* button = new QPushButton(windowContent);
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
windowContentLayout->addWidget(button);
}
{
// Text+Icon, expanding size.
auto* button = new QPushButton(windowContent);
button->setText("Button");
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
windowContentLayout->addWidget(button);
}
{
// Text+Icon+Menu, expanding size
auto* button = new QPushButton(windowContent);
button->setText("Button");
- button->setIcon(QIcon(":/refresh.svg"));
+ button->setIcon(getTestQIcon());
auto* menu = new QMenu("ButtonMenu");
for (auto i = 0; i < 3; ++i) {
@@ -422,7 +444,7 @@ struct SandboxWindow::Impl {
const auto tristate = i > 1;
checkbox->setChecked(checked);
- checkbox->setIcon(QIcon(":/refresh.svg"));
+ checkbox->setIcon(getTestQIcon());
checkbox->setText(QString("%1 checkbox %2 with a very long text").arg(tristate ? "Tristate" : "Normal").arg(i));
checkbox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
checkbox->setTristate(tristate);
@@ -436,7 +458,7 @@ struct SandboxWindow::Impl {
for (auto i = 0; i < 2; ++i) {
auto* radiobutton = new QRadioButton(windowContent);
radiobutton->setChecked(true);
- radiobutton->setIcon(QIcon(":/refresh.svg"));
+ radiobutton->setIcon(getTestQIcon());
radiobutton->setText(QString("RadioButton %1 with a very long text").arg(i));
radiobutton->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
radioGroup->addButton(radiobutton);
@@ -446,21 +468,19 @@ struct SandboxWindow::Impl {
void setupUI_commandLinkButton() {
{
- const QIcon icon(":/plus_24.svg");
auto* button = new CommandLinkButton(windowContent);
button->setText("First Line with a very long text that should be cropped");
button->setDescription("Second Line that could be very long and should be cropped");
- button->setIcon(icon);
+ button->setIcon(getTestQIcon({ 24, 24 }));
button->setDefault(true);
button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
windowContentLayout->addWidget(button);
}
{
- const QIcon icon(":/plus_24.svg");
auto* button = new CommandLinkButton(windowContent);
button->setText("First Line with a very long text that should be cropped");
button->setDescription("Second Line that could be very long and should be cropped");
- button->setIcon(icon);
+ button->setIcon(getTestQIcon({ 24, 24 }));
button->setDefault(false);
button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
windowContentLayout->addWidget(button);
@@ -586,7 +606,7 @@ struct SandboxWindow::Impl {
combobox->setEditable(true);
for (auto i = 0; i < 4; ++i) {
- combobox->addItem(QIcon(":/refresh.svg"), QString("Editable comboBox item %1").arg(i));
+ combobox->addItem(getTestQIcon(), QString("Editable comboBox item %1").arg(i));
}
auto* model = qobject_cast(combobox->model());
auto* item = model->item(2);
@@ -597,26 +617,33 @@ struct SandboxWindow::Impl {
// Non-editable
{
auto* combobox = new QComboBox(windowContent);
- combobox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ combobox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
combobox->setFocusPolicy(Qt::StrongFocus);
for (auto i = 0; i < 4; ++i) {
- combobox->addItem(QIcon(":/refresh.svg"), QString("ComboBox item %1").arg(i));
+ combobox->addItem(getTestQIcon(), QString("ComboBox item %1").arg(i));
}
windowContentLayout->addWidget(combobox);
}
}
+ void setupUI_fontComboBox() {
+ auto* combobox = new QFontComboBox(windowContent);
+ combobox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ combobox->setFocusPolicy(Qt::StrongFocus);
+ windowContentLayout->addWidget(combobox);
+ }
+
void setupUI_listView() {
auto* listView = new QListWidget(windowContent);
listView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
//listView->setAlternatingRowColors(true);
listView->setIconSize(QSize(32, 32));
- for (auto i = 0; i < 6; ++i) {
+ for (auto i = 0; i < 2; ++i) {
auto* item = new QListWidgetItem(
- QIcon(":/refresh.svg"), QString("Item #%1 with very long text that can be elided").arg(i), listView);
+ getTestQIcon(), QString("Item #%1 with very long text that can be elided").arg(i), listView);
item->setFlags(item->flags() | Qt::ItemFlag::ItemIsUserCheckable);
item->setCheckState(i % 2 ? Qt ::CheckState::Checked : Qt::CheckState::Unchecked);
//item->setForeground(i % 2 ? Qt::red : Qt::blue);
@@ -626,7 +653,6 @@ struct SandboxWindow::Impl {
windowContentLayout->addWidget(listView);
// Context menu.
- qDebug() << listView->contextMenuPolicy();
listView->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
QObject::connect(listView, &QListView::customContextMenuRequested, listView, [listView](const QPoint& pos) {
if (const auto item = listView->itemAt(pos)) {
@@ -660,7 +686,6 @@ struct SandboxWindow::Impl {
constexpr auto rowCount = 3;
tableView->setColumnCount(columnCount);
tableView->setRowCount(rowCount);
- const QIcon icon(":/scene_object.svg");
std::vector columnAlignments;
columnAlignments.resize(columnCount);
@@ -671,21 +696,21 @@ struct SandboxWindow::Impl {
for (auto col = 0; col < columnCount; ++col) {
auto* item = new QTableWidgetItem(QString("Column %1").arg(col + 1));
- item->setIcon(icon);
+ item->setIcon(getTestQIcon({ 16, 16 }, true));
item->setTextAlignment(columnAlignments.at(col));
tableView->setHorizontalHeaderItem(col, item);
}
for (auto row = 0; row < rowCount; ++row) {
auto* item = new QTableWidgetItem(QString("Row %1").arg(row + 1));
- item->setIcon(icon);
+ item->setIcon(getTestQIcon({ 16, 16 }, true));
tableView->setVerticalHeaderItem(row, item);
}
for (auto row = 0; row < rowCount; ++row) {
for (auto col = 0; col < columnCount; ++col) {
auto* item = new QTableWidgetItem(QString("Item at %1, %2").arg(row + 1).arg(col + 1));
- item->setIcon(icon);
+ item->setIcon(getTestQIcon({ 16, 16 }, true));
item->setTextAlignment(columnAlignments.at(col));
item->setFlags(Qt::ItemFlag::ItemIsEditable | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled);
item->setData(Qt::DisplayRole, QVariant::fromValue(true));
@@ -703,37 +728,39 @@ struct SandboxWindow::Impl {
treeWidget->setColumnCount(1);
treeWidget->setHeaderHidden(true);
treeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
- qlementineStyle->setAutoIconColor(treeWidget, oclero::qlementine::AutoIconColor::None);
+
+ oclero::qlementine::appStyle()->setAutoIconColor(treeWidget, oclero::qlementine::AutoIconColor::None);
for (auto i = 0; i < 3; ++i) {
auto* root = new QTreeWidgetItem(treeWidget);
root->setText(0, QString("Root %1").arg(i + 1));
- root->setIcon(0, QIcon(":/scene_object.svg"));
+ root->setIcon(0, getTestQIcon({ 16, 16 }, true));
root->setText(1, QString("Column 2 of Root %1").arg(i + 1));
for (auto j = 0; j < 3; ++j) {
auto* child = new QTreeWidgetItem(root);
child->setText(0, QString("Child %1 of Root %2").arg(j).arg(i));
- child->setIcon(0, j == 2 ? QIcon(":/scene_light.svg") : QIcon(":/scene_object.svg"));
+ child->setIcon(0, getTestQIcon({ 16, 16 }, true));
child->setText(1, QString("Column 2 of Child %1 of Root %2").arg(j).arg(i));
for (auto k = 0; k < 3; ++k) {
auto* subChild = new QTreeWidgetItem(child);
subChild->setText(0, QString("Child %1 of Child %2 of Root %3").arg(k).arg(j).arg(i));
- subChild->setIcon(0, QIcon(":/scene_material.svg"));
+ subChild->setIcon(0, getTestQIcon({ 16, 16 }, true));
subChild->setText(1, QString("Column 2 of Child %1 of Child %2 of Root %3").arg(k).arg(j).arg(i));
}
}
}
- treeWidget->topLevelItem(0)->setSelected(true), windowContentLayout->addWidget(treeWidget);
+ treeWidget->topLevelItem(0)->setSelected(true);
+ windowContentLayout->addWidget(treeWidget);
}
void setupUI_menuBar() const {
auto* menuBar = owner.menuBar();
// NB: it looks like MacOS' native menu bar has an issue with QIcon, so we have to force
// it to generate icons for High-DPI screens.
- const auto icon = makeIconFromSvg(":/refresh.svg", owner.iconSize());
+ const auto icon = getTestQIcon();
for (auto i = 0; i < 5; ++i) {
auto* menu = menuBar->addMenu(QString("Menu &%1").arg(i));
@@ -776,14 +803,14 @@ struct SandboxWindow::Impl {
void setupUI_toolButton() {
auto* toolButton = new QToolButton(toolbar);
- toolButton->setIcon(QIcon(":/refresh.svg"));
+ toolButton->setIcon(getTestQIcon());
toolButton->setText(QString("Button with a very long text that can be elided"));
toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
toolButton->setCheckable(false);
toolButton->setChecked(false);
{
- const auto icon = QIcon(":/refresh.svg");
+ const auto icon = getTestQIcon();
auto* subMenu = new QMenu("Menu title", toolButton);
toolButton->setMenu(subMenu);
subMenu->addAction(icon, "Sub Action 1");
@@ -797,7 +824,7 @@ struct SandboxWindow::Impl {
}
void setupUI_toolButtonsVariants() {
- const auto icon = QIcon(":/refresh.svg");
+ const auto icon = getTestQIcon();
toolbar = owner.addToolBar("ToolBar name");
//toolbar->set
@@ -884,11 +911,10 @@ struct SandboxWindow::Impl {
}
void setupUI_tabBar() {
- const QIcon icon(":/scene_object.svg");
auto* tabBar = new QTabBar(windowContent);
tabBar->setFocusPolicy(Qt::NoFocus);
tabBar->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
- qlementineStyle->setAutoIconColor(tabBar, oclero::qlementine::AutoIconColor::None);
+ oclero::qlementine::appStyle()->setAutoIconColor(tabBar, oclero::qlementine::AutoIconColor::None);
// QTabBar features.
tabBar->setTabsClosable(true);
@@ -909,7 +935,7 @@ struct SandboxWindow::Impl {
const auto tabText = QString(tabTextList.join(" ").append(QString(" %1").arg(i + 1)));
if (i % 3 == 0) {
- tabBar->addTab(icon, tabText);
+ tabBar->addTab(getTestQIcon({ 16, 16 }, true), tabText);
} else {
tabBar->addTab(tabText);
}
@@ -935,7 +961,6 @@ struct SandboxWindow::Impl {
}
void setupUI_tabWidget() {
- const QStringList icons = { ":/scene_object.svg", ":/scene_light.svg", ":/scene_material.svg" };
auto* tabWidget = new QTabWidget(windowContent);
tabWidget->setDocumentMode(false);
@@ -964,7 +989,7 @@ struct SandboxWindow::Impl {
tabTextList.append("Tab");
}
const auto tabText = QString(tabTextList.join(" ").append(QString(" %1").arg(i + 1)));
- const auto icon = QIcon(icons.at(i % icons.size()));
+ const auto icon = getTestQIcon();
tabWidget->addTab(tabContent, icon, tabText);
}
}
@@ -1067,24 +1092,24 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
treeWidget->setHeaderHidden(true);
treeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
treeWidget->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
- qlementineStyle->setAutoIconColor(treeWidget, oclero::qlementine::AutoIconColor::None);
+ oclero::qlementine::appStyle()->setAutoIconColor(treeWidget, oclero::qlementine::AutoIconColor::None);
for (auto i = 0; i < 3; ++i) {
auto* root = new QTreeWidgetItem(treeWidget);
root->setText(0, QString("Root %1").arg(i + 1));
- root->setIcon(0, QIcon(":/scene_object.svg"));
+ root->setIcon(0, getTestQIcon({ 16, 16 }, true));
root->setText(1, QString("Column 2 of Root %1").arg(i + 1));
for (auto j = 0; j < 3; ++j) {
auto* child = new QTreeWidgetItem(root);
child->setText(0, QString("Child %1 of Root %2").arg(j).arg(i));
- child->setIcon(0, j == 2 ? QIcon(":/scene_light.svg") : QIcon(":/scene_object.svg"));
+ child->setIcon(0, getTestQIcon({ 16, 16 }, true));
child->setText(1, QString("Column 2 of Child %1 of Root %2").arg(j).arg(i));
for (auto k = 0; k < 3; ++k) {
auto* subChild = new QTreeWidgetItem(child);
subChild->setText(0, QString("Child %1 of Child %2 of Root %3").arg(k).arg(j).arg(i));
- subChild->setIcon(0, QIcon(":/scene_material.svg"));
+ subChild->setIcon(0, getTestQIcon({ 16, 16 }, true));
subChild->setText(1, QString("Column 2 of Child %1 of Child %2 of Root %3").arg(k).arg(j).arg(i));
}
}
@@ -1102,7 +1127,7 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
for (auto i = 0; i < 3; ++i) {
auto* item = new QListWidgetItem(
- QIcon(":/refresh.svg"), QString("Item #%1 with very long text that can be elided").arg(i), listView);
+ getTestQIcon(), QString("Item #%1 with very long text that can be elided").arg(i), listView);
item->setFlags(item->flags() | Qt::ItemFlag::ItemIsUserCheckable);
item->setCheckState(i % 2 ? Qt ::CheckState::Checked : Qt::CheckState::Unchecked);
@@ -1120,7 +1145,7 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
tableView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
tableView->setColumnCount(columnCount);
tableView->setRowCount(rowCount);
- QIcon icon(":/refresh.svg");
+ auto icon = getTestQIcon();
auto* headerItem = new QTableWidgetItem(icon, "A veeeeeery long header label");
tableView->setHorizontalHeaderItem(0, headerItem);
tableView->setSelectionBehavior(QTableView::SelectionBehavior::SelectRows);
@@ -1151,29 +1176,54 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
windowContent->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
{
auto* container = new CustomBgWidget(windowContent);
+ container->bgColor = QColor{ 255, 0, 0, 10 };
+ container->borderColor = QColor{ 255, 0, 0, 40 };
auto* containerLayout = new QVBoxLayout(container);
containerLayout->setContentsMargins(10, 10, 10, 10);
container->setLayout(containerLayout);
auto* expander = new Expander(container);
+ expander->setOrientation(Qt::Orientation::Horizontal);
auto* expanderContent = new CustomBgWidget(expander);
- expanderContent->bgColor = QColor{ 255, 127, 0 };
+ expanderContent->bgColor = QColor{ 0, 0, 255, 40 };
+ expanderContent->borderColor = QColor{ 0, 0, 255, 127 };
expanderContent->customSizeHint = QSize{ 150, 100 };
expanderContent->showBounds = true;
expander->setContent(expanderContent);
auto* checkBox = new QCheckBox("Expanded", container);
+ checkBox->setChecked(expander->expanded());
QObject::connect(checkBox, &QCheckBox::toggled, &owner, [expander](bool checked) {
expander->setExpanded(checked);
});
- auto* button = new QPushButton("Increase content height", container);
- QObject::connect(button, &QPushButton::clicked, &owner, [expanderContent]() {
- expanderContent->customSizeHint.rheight() += 20;
+ auto* vLayout = new QVBoxLayout();
+ auto* buttonGroup = new QButtonGroup(windowContent);
+ for (auto orientation : { Qt::Vertical, Qt::Horizontal }) {
+ auto* radioButton = new QRadioButton(orientation == Qt::Vertical ? "Vertical" : "Horizontal", container);
+ radioButton->setChecked(orientation == expander->orientation());
+ buttonGroup->addButton(radioButton);
+ vLayout->addWidget(radioButton);
+ QObject::connect(radioButton, &QRadioButton::toggled, &owner, [expander, orientation](bool checked) {
+ if (checked) {
+ expander->setOrientation(orientation);
+ }
+ });
+ }
+
+ auto* button = new QPushButton("Increase content animated dimension", container);
+ QObject::connect(button, &QPushButton::clicked, &owner, [expanderContent, expander]() {
+ if (expander->orientation() == Qt::Vertical) {
+ expanderContent->customSizeHint.rheight() += 20;
+ } else {
+ expanderContent->customSizeHint.rwidth() += 20;
+ }
expanderContent->updateGeometry();
});
+
containerLayout->addWidget(checkBox);
containerLayout->addWidget(button);
+ containerLayout->addLayout(vLayout);
containerLayout->addWidget(expander);
windowContentLayout->addWidget(container);
@@ -1320,7 +1370,7 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
}
void setupUI_navigationBar() {
- const QIcon dummyIcon(":/refresh.svg");
+ const auto dummyIcon = getTestQIcon();
auto* navBar = new NavigationBar(windowContent);
for (auto i = 0; i < 3; ++i) {
@@ -1336,7 +1386,7 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
}
void setupUI_switch() {
- const QIcon dummyIcon(":/refresh.svg");
+ const auto dummyIcon = getTestQIcon();
auto* switchWidget = new Switch(windowContent);
switchWidget->setText("Label of the Switch");
switchWidget->setIcon(dummyIcon);
@@ -1435,7 +1485,7 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
}
void setupUI_lineEditStatus() {
- const QIcon dummyIcon(":/refresh.svg");
+ const auto dummyIcon = getTestQIcon();
auto* lineEdit = new LineEdit(windowContent);
lineEdit->setText("Label of the Switch");
@@ -1553,78 +1603,66 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
windowContentLayout->addWidget(plainWidget);
}
-
- SandboxWindow& owner;
- QString lastJsonThemePath;
- QPointer qlementineStyle;
-
- QWidget* windowContent{ nullptr };
- QBoxLayout* windowContentLayout{ nullptr };
- QScrollArea* globalScrollArea{ nullptr };
- QToolBar* toolbar{ nullptr };
};
-SandboxWindow::SandboxWindow(QWidget* parent)
+SandboxWindow::SandboxWindow(ThemeManager* themeManager, QWidget* parent)
: QMainWindow(parent)
- , _impl(new Impl(*this)) {
- setWindowIcon(QIcon(QStringLiteral(":/qlementine_icon.ico")));
+ , _impl(new Impl(*this, themeManager)) {
+ setWindowIcon(QIcon(QStringLiteral(":/sandbox/qlementine_icon.ico")));
_impl->beginSetupUI();
{
// Uncomment the line to show the corresponding widget.
- // _impl->setupUI_label();
- // _impl->setupUI_button();
- // _impl->setupUI_buttonVariants();
- // _impl->setupUI_checkbox();
- // _impl->setupUI_radioButton();
- // _impl->setupUI_commandLinkButton();
- // _impl->setupUI_sliderAndProgressBar();
- // _impl->setupUI_sliderWithTicks();
- // _impl->setupUI_lineEdit();
- // _impl->setupUI_textEdit();
- // _impl->setupUI_plainTextEdit();
- // _impl->setupUI_dial();
- // _impl->setupUI_spinBox();
- // _impl->setupUI_comboBox();
- // _impl->setupUI_listView();
- // _impl->setupUI_treeWidget();
- // _impl->setupUI_table();
- // _impl->setupUI_menuBar();
- // _impl->setupUI_toolButton();
- // _impl->setupUI_toolButtonsVariants();
- // _impl->setupUI_tabBar();
- // _impl->setupUI_tabWidget();
- // _impl->setupUI_groupBox();
- // _impl->setupUI_treeView();
- // _impl->setupUI_focus();
- // _impl->setupUI_specialProgressBar();
- // _impl->setupUI_lineEditStatus();
- // _impl->setupUI_dateTimeEdit();
- // _impl->setupUI_contextMenu();
-
- // _impl->setupUI_switch();
- // _impl->setupUI_expander();
- // _impl->setupUI_popover();
- // _impl->setupUI_navigationBar();
- // _impl->setupUI_badge();
- // _impl->setupUI_colorButton();
-
- // _impl->setupUI_messageBoxIcons();
- // _impl->setupUI_fontMetricsTests();
- // _impl->setupUI_blur();
- // _impl->setupUI_themeEditor();
- // _impl->setupUI_messageBox();
+ // _impl->setupUI_label();
+ // _impl->setupUI_button();
+ // _impl->setupUI_buttonVariants();
+ // _impl->setupUI_checkbox();
+ // _impl->setupUI_radioButton();
+ // _impl->setupUI_commandLinkButton();
+ // _impl->setupUI_sliderAndProgressBar();
+ // _impl->setupUI_sliderWithTicks();
+ // _impl->setupUI_lineEdit();
+ // _impl->setupUI_textEdit();
+ // _impl->setupUI_plainTextEdit();
+ // _impl->setupUI_dial();
+ // _impl->setupUI_spinBox();
+ // _impl->setupUI_comboBox();
+ // _impl->setupUI_listView();
+ // _impl->setupUI_treeWidget();
+ // _impl->setupUI_table();
+ // _impl->setupUI_menuBar();
+ // _impl->setupUI_toolButton();
+ // _impl->setupUI_toolButtonsVariants();
+ // _impl->setupUI_tabBar();
+ // _impl->setupUI_tabWidget();
+ // _impl->setupUI_groupBox();
+ // _impl->setupUI_treeView();
+ // _impl->setupUI_focus();
+ // _impl->setupUI_specialProgressBar();
+ // _impl->setupUI_lineEditStatus();
+ // _impl->setupUI_dateTimeEdit();
+ // _impl->setupUI_contextMenu();
+ // _impl->setupUI_fontComboBox();
+
+ // _impl->setupUI_switch();
+ // _impl->setupUI_expander();
+ // _impl->setupUI_popover();
+ // _impl->setupUI_navigationBar();
+ // _impl->setupUI_badge();
+ // _impl->setupUI_colorButton();
+ // _impl->setupUI_messageBoxIcons();
+
+ // _impl->setupUI_fontMetricsTests();
+ // _impl->setupUI_blur();
+ // _impl->setupUI_themeEditor();
+ // _impl->setupUI_messageBox();
}
_impl->endSetupUI();
+ oclero::qlementine::centerWidget(this);
}
SandboxWindow::~SandboxWindow() = default;
-void SandboxWindow::setCustomStyle(QlementineStyle* style) {
- _impl->qlementineStyle = style;
- _impl->lastJsonThemePath = QStringLiteral(":/light.json");
-}
-
bool SandboxWindow::eventFilter(QObject* watched, QEvent* event) {
if (event->type() == QEvent::Type::Close) {
qApp->closeAllWindows();
diff --git a/sandbox/src/SandboxWindow.hpp b/sandbox/src/SandboxWindow.hpp
index 4c1a4ea..4acbc24 100644
--- a/sandbox/src/SandboxWindow.hpp
+++ b/sandbox/src/SandboxWindow.hpp
@@ -7,16 +7,15 @@
namespace oclero::qlementine {
class QlementineStyle;
-}
+class ThemeManager;
+} // namespace oclero::qlementine
namespace oclero::qlementine::sandbox {
class SandboxWindow : public QMainWindow {
public:
- SandboxWindow(QWidget* parent = nullptr);
+ SandboxWindow(ThemeManager* themeManager = nullptr, QWidget* parent = nullptr);
~SandboxWindow();
- void setCustomStyle(QlementineStyle* style);
-
bool eventFilter(QObject* watched, QEvent* event) override;
private:
diff --git a/sandbox/src/main.cpp b/sandbox/src/main.cpp
index 15ab031..cc0dd0c 100644
--- a/sandbox/src/main.cpp
+++ b/sandbox/src/main.cpp
@@ -2,17 +2,13 @@
// SPDX-License-Identifier: MIT
#include
-#include
-#include
#include
-#include
+#include
#include "SandboxWindow.hpp"
-//#include "CsdWindow.hpp"
#define USE_CUSTOM_STYLE 1
-//#define CSD_WINDOW 0
int main(int argc, char* argv[]) {
// Must be set before creating a QApplication.
@@ -21,30 +17,30 @@ int main(int argc, char* argv[]) {
QApplication qApplication(argc, argv);
// Must be set after creating a QApplication.
- QGuiApplication::setApplicationDisplayName("sandbox");
- QCoreApplication::setApplicationName("sandbox");
- QGuiApplication::setDesktopFileName("sandbox");
+ QGuiApplication::setApplicationDisplayName("Sandbox");
+ QCoreApplication::setApplicationName("Sandbox");
+ QGuiApplication::setDesktopFileName("Sandbox");
QCoreApplication::setOrganizationName("oclero");
QCoreApplication::setOrganizationDomain("olivierclero.com");
QCoreApplication::setApplicationVersion("1.0.0");
QApplication::setWindowIcon(QIcon(QStringLiteral(":/qlementine_icon.ico")));
- // Set custom QStyle.
#if USE_CUSTOM_STYLE
- auto* const style = new oclero::qlementine::QlementineStyle(&qApplication);
+ // Set custom QStyle.
+ auto* style = new oclero::qlementine::QlementineStyle(&qApplication);
style->setAnimationsEnabled(true);
- style->setUseMenuForComboBoxPopup(false);
style->setAutoIconColor(oclero::qlementine::AutoIconColor::TextColor);
- style->setThemeJsonPath(QStringLiteral(":/light.json"));
qApplication.setStyle(style);
-#endif
- auto window = std::make_unique();
-#if USE_CUSTOM_STYLE
- window->setCustomStyle(style);
+ // Theme manager.
+ auto* themeManager = new oclero::qlementine::ThemeManager(style);
+ themeManager->loadDirectory(":/showcase/themes");
+
+ // Define theme on QStyle.
+ themeManager->setCurrentTheme("Light");
#endif
- oclero::qlementine::centerWidget(window.get());
+ auto window = std::make_unique(themeManager);
window->show();
return qApplication.exec();
diff --git a/scripts/format.sh b/scripts/format.sh
index a80debe..b32a2e1 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -1,3 +1,3 @@
#!/bin/bash
-find $(dirname "$0")/../lib/src $(dirname "$0")/../lib/include -iname *.hpp -o -iname *.cpp | xargs clang-format -i
+find $(dirname "$0")/../lib/src $(dirname "$0")/../lib/include $(dirname "$0")/../sandbox/src $(dirname "$0")/../showcase/src -iname *.hpp -o -iname *.cpp | xargs clang-format -i
diff --git a/showcase/CMakeLists.txt b/showcase/CMakeLists.txt
new file mode 100644
index 0000000..ca1a1aa
--- /dev/null
+++ b/showcase/CMakeLists.txt
@@ -0,0 +1,66 @@
+set(SHOWCASE_NAME "showcase")
+
+# Dependency: qlementine-icons
+include(FetchContent)
+FetchContent_Declare(qlementine_icons
+ GIT_REPOSITORY "https://github.com/oclero/qlementine-icons.git"
+ GIT_TAG 0a269d7c4eb77fdc8ca92a7fb2ceae1baae29727 #v1.5.0
+)
+FetchContent_MakeAvailable(qlementine_icons)
+set_target_properties(qlementine_icons
+ PROPERTIES
+ FOLDER dependencies
+)
+
+if(APPLE)
+ set(APP_ICON_MACOS "${CMAKE_SOURCE_DIR}/branding/icon/icon.icns")
+ set_source_files_properties(${APP_ICON_MACOS}
+ PROPERTIES
+ MACOSX_PACKAGE_LOCATION "Resources"
+ )
+endif()
+
+set(SOURCES
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/ShowcaseWindow.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/ShowcaseWindow.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/resources/showcase.qrc
+)
+
+qt_add_executable(${SHOWCASE_NAME}
+ WIN32 MACOSX_BUNDLE
+ ${SOURCES}
+)
+
+target_link_libraries(${SHOWCASE_NAME}
+ PUBLIC
+ oclero::qlementine
+ oclero::qlementine_icons
+)
+
+install(
+ TARGETS ${SHOWCASE_NAME}
+ BUNDLE DESTINATION .
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+)
+
+set_target_properties(${SHOWCASE_NAME}
+ PROPERTIES
+ INTERNAL_CONSOLE OFF
+ EXCLUDE_FROM_ALL OFF
+ FOLDER "tools"
+ CMAKE_AUTOMOC ON
+ CMAKE_AUTORCC ON
+ CMAKE_AUTOUIC ON
+ MACOSX_BUNDLE_GUI_IDENTIFIER "oclero.qlementine.${SHOWCASE_NAME}"
+ MACOSX_BUNDLE_BUNDLE_NAME "Showcase"
+ MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
+ MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION}
+ MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION}
+ MACOSX_BUNDLE_ICON_FILE "icon.icns"
+ MACOSX_BUNDLE_COPYRIGHT ${PROJECT_COPYRIGHT}
+ XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "${XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED}"
+ XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY}"
+ XCODE_ATTRIBUTE_CODE_SIGN_STYLE "${XCODE_ATTRIBUTE_CODE_SIGN_STYLE}"
+ XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS OFF
+)
diff --git a/showcase/resources/icons/cube-green.svg b/showcase/resources/icons/cube-green.svg
new file mode 100644
index 0000000..c3c2b4a
--- /dev/null
+++ b/showcase/resources/icons/cube-green.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/showcase/resources/icons/cube-red.svg b/showcase/resources/icons/cube-red.svg
new file mode 100644
index 0000000..de7c25a
--- /dev/null
+++ b/showcase/resources/icons/cube-red.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/showcase/resources/icons/cube-yellow.svg b/showcase/resources/icons/cube-yellow.svg
new file mode 100644
index 0000000..48e79d2
--- /dev/null
+++ b/showcase/resources/icons/cube-yellow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/showcase/resources/qlementine_icon.icns b/showcase/resources/qlementine_icon.icns
new file mode 100644
index 0000000..32d7547
Binary files /dev/null and b/showcase/resources/qlementine_icon.icns differ
diff --git a/showcase/resources/qlementine_icon.ico b/showcase/resources/qlementine_icon.ico
new file mode 100644
index 0000000..49774ac
Binary files /dev/null and b/showcase/resources/qlementine_icon.ico differ
diff --git a/showcase/resources/showcase.qrc b/showcase/resources/showcase.qrc
new file mode 100644
index 0000000..55f4ce2
--- /dev/null
+++ b/showcase/resources/showcase.qrc
@@ -0,0 +1,11 @@
+
+
+ icons/cube-green.svg
+ icons/cube-red.svg
+ icons/cube-yellow.svg
+ themes/dark.json
+ themes/light.json
+ qlementine_icon.ico
+ qlementine_icon.icns
+
+
diff --git a/showcase/resources/themes/dark.json b/showcase/resources/themes/dark.json
new file mode 100644
index 0000000..5921357
--- /dev/null
+++ b/showcase/resources/themes/dark.json
@@ -0,0 +1,91 @@
+{
+ "meta": {
+ "author": "Olivier Cléro",
+ "name": "Dark",
+ "version": "1.5.0"
+ },
+
+ "backgroundColorMain1": "#1f2127",
+ "backgroundColorMain2": "#282b33",
+ "backgroundColorMain3": "#333848",
+ "backgroundColorMain4": "#333848",
+
+ "backgroundColorWorkspace": "#17181c",
+ "backgroundColorTabBar": "#1e2026",
+
+ "borderColor": "#40485a",
+ "borderColorHovered": "#4a5670",
+ "borderColorPressed": "#5b6e98",
+ "borderColorDisabled": "#2f343f",
+
+ "focusColor": "#3097ff6a",
+
+ "neutralColor": "#4c5368",
+ "neutralColorHovered": "#535c78",
+ "neutralColorPressed": "#5b6688",
+ "neutralColorDisabled": "#2d313b",
+
+ "primaryColor": "#5086ff",
+ "primaryColorHovered": "#6494ff",
+ "primaryColorPressed": "#7aa3ff",
+ "primaryColorDisabled": "#2c3448",
+
+ "primaryAlternativeColor": "#3161f8",
+ "primaryAlternativeColorHovered": "#4571fe",
+ "primaryAlternativeColorPressed": "#5a82ff",
+ "primaryAlternativeColorDisabled": "#293346",
+
+ "primaryColorForeground": "#ffffff",
+ "primaryColorForegroundHovered": "#ffffff",
+ "primaryColorForegroundPressed": "#ffffff",
+ "primaryColorForegroundDisabled": "#455170",
+
+ "secondaryColor": "#ffffff",
+ "secondaryColorHovered": "#d5d5d5",
+ "secondaryColorPressed": "#ebebeb",
+ "secondaryColorDisabled": "#ffffff33",
+
+ "secondaryAlternativeColor": "#67718d",
+ "secondaryAlternativeColorHovered": "#8b93ab",
+ "secondaryAlternativeColorPressed": "#9ca4bc",
+ "secondaryAlternativeColorDisabled": "#575f763f",
+
+ "secondaryColorForeground": "#282b33",
+ "secondaryColorForegroundHovered": "#282b33",
+ "secondaryColorForegroundPressed": "#282b33",
+ "secondaryColorForegroundDisabled": "#282b333f",
+
+ "semiTransparentColor1": "#b7c9ff18",
+ "semiTransparentColor2": "#b7c9ff23",
+ "semiTransparentColor3": "#b7c9ff28",
+ "semiTransparentColor4": "#b7c9ff2d",
+
+ "shadowColor1": "#00000066",
+ "shadowColor2": "#000000bb",
+ "shadowColor3": "#000000ff",
+
+ "statusColorForeground": "#ffffff",
+ "statusColorForegroundHovered": "#ffffff",
+ "statusColorForegroundPressed": "#ffffff",
+ "statusColorForegroundDisabled": "#ffffff26",
+
+ "statusColorError": "#e96b72",
+ "statusColorErrorHovered": "#f47c83",
+ "statusColorErrorPressed": "#ff9197",
+ "statusColorErrorDisabled": "#3f333b",
+
+ "statusColorInfo": "#1ba8d5",
+ "statusColorInfoHovered": "#1eb5e5",
+ "statusColorInfoPressed": "#29c0f0",
+ "statusColorInfoDisabled": "#283345",
+
+ "statusColorSuccess": "#2bb5a0",
+ "statusColorSuccessHovered": "#3cbfab",
+ "statusColorSuccessPressed": "#4ecdb9",
+ "statusColorSuccessDisabled": "#28363c",
+
+ "statusColorWarning": "#fbc064",
+ "statusColorWarningHovered": "#ffcf6c",
+ "statusColorWarningPressed": "#ffd880",
+ "statusColorWarningDisabled": "#393737"
+}
diff --git a/sandbox/resources/light.json b/showcase/resources/themes/light.json
similarity index 87%
rename from sandbox/resources/light.json
rename to showcase/resources/themes/light.json
index 2c7c841..963e6a6 100644
--- a/sandbox/resources/light.json
+++ b/showcase/resources/themes/light.json
@@ -1,71 +1,91 @@
{
+ "meta": {
+ "author": "Olivier Cléro",
+ "name": "Light",
+ "version": "1.5.0"
+ },
+
"backgroundColorMain1": "#ffffff",
"backgroundColorMain2": "#f3f3f3",
"backgroundColorMain3": "#e3e3e3",
- "backgroundColorMain4": "#dcdcdc",
+ "backgroundColorMain4": "#dfdfdf",
+
+ "backgroundColorWorkspace": "#b7b7b7",
+ "backgroundColorTabBar": "#dfdfdf",
+
"borderColor": "#d3d3d3",
"borderColorDisabled": "#e9e9e9",
"borderColorHovered": "#b3b3b3",
"borderColorPressed": "#a3a3a3",
+
"focusColor": "#40a9ff66",
- "meta": {
- "author": "Olivier Cléro",
- "name": "Light",
- "version": "1.4.0"
- },
- "neutralColor": "#e1e1e1",
+
+ "neutralColor": "#d1d1d1",
+ "neutralColorHovered": "#d3d3d3",
+ "neutralColorPressed": "#d5d5d5",
"neutralColorDisabled": "#eeeeee",
- "neutralColorHovered": "#d9d9d9",
- "neutralColorPressed": "#d2d2d2",
+
+ "primaryColor": "#1890ff",
+ "primaryColorHovered": "#2c9dff",
+ "primaryColorPressed": "#40a9ff",
+ "primaryColorDisabled": "#d1e9ff",
+
"primaryAlternativeColor": "#106ef9",
"primaryAlternativeColorDisabled": "#a9d6ff",
"primaryAlternativeColorHovered": "#107bfd",
"primaryAlternativeColorPressed": "#108bfd",
- "primaryColor": "#1890ff",
- "primaryColorDisabled": "#d1e9ff",
+
"primaryColorForeground": "#ffffff",
"primaryColorForegroundDisabled": "#ecf6ff",
"primaryColorForegroundHovered": "#ffffff",
"primaryColorForegroundPressed": "#ffffff",
- "primaryColorHovered": "#2c9dff",
- "primaryColorPressed": "#40a9ff",
+
+ "secondaryColor": "#404040",
+ "secondaryColorHovered": "#333333",
+ "secondaryColorPressed": "#262626",
+ "secondaryColorDisabled": "#d4d4d4",
+
"secondaryAlternativeColor": "#909090",
"secondaryAlternativeColorDisabled": "#c3c3c3",
"secondaryAlternativeColorHovered": "#747474",
"secondaryAlternativeColorPressed": "#828282",
- "secondaryColor": "#404040",
- "secondaryColorDisabled": "#d4d4d4",
+
"secondaryColorForeground": "#ffffff",
"secondaryColorForegroundDisabled": "#ededed",
"secondaryColorForegroundHovered": "#ffffff",
"secondaryColorForegroundPressed": "#ffffff",
- "secondaryColorHovered": "#333333",
- "secondaryColorPressed": "#262626",
+
"semiTransparentColor1": "#0000000a",
"semiTransparentColor2": "#00000019",
"semiTransparentColor3": "#00000021",
"semiTransparentColor4": "#00000028",
+
"shadowColor1": "#00000020",
"shadowColor2": "#00000040",
"shadowColor3": "#00000060",
+
"statusColorError": "#e96b72",
- "statusColorErrorDisabled": "#f9dadc",
"statusColorErrorHovered": "#f47c83",
"statusColorErrorPressed": "#ff9197",
+ "statusColorErrorDisabled": "#f9dadc",
+
"statusColorForeground": "#ffffff",
- "statusColorForegroundDisabled": "#ffffff99",
"statusColorForegroundHovered": "#ffffff",
"statusColorForegroundPressed": "#ffffff",
+ "statusColorForegroundDisabled": "#ffffff99",
+
"statusColorInfo": "#1ba8d5",
- "statusColorInfoDisabled": "#c7eaf5",
"statusColorInfoHovered": "#1eb5e5",
"statusColorInfoPressed": "#29c0f0",
+ "statusColorInfoDisabled": "#c7eaf5",
+
"statusColorSuccess": "#2bb5a0",
- "statusColorSuccessDisabled": "#d5f0ec",
"statusColorSuccessHovered": "#3cbfab",
"statusColorSuccessPressed": "#4ecdb9",
+ "statusColorSuccessDisabled": "#d5f0ec",
+
"statusColorWarning": "#fbc064",
- "statusColorWarningDisabled": "#feefd8",
"statusColorWarningHovered": "#ffcf6c",
- "statusColorWarningPressed": "#ffd880"
+ "statusColorWarningPressed": "#ffd880",
+ "statusColorWarningDisabled": "#feefd8"
}
diff --git a/showcase/src/ShowcaseWindow.cpp b/showcase/src/ShowcaseWindow.cpp
new file mode 100644
index 0000000..6ceb9e8
--- /dev/null
+++ b/showcase/src/ShowcaseWindow.cpp
@@ -0,0 +1,746 @@
+// SPDX-FileCopyrightText: Olivier Cléro
+// SPDX-License-Identifier: MIT
+
+#include "ShowcaseWindow.hpp"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include