diff --git a/ui/resources/resources.qrc b/ui/resources/resources.qrc
index ea38850..716e8a0 100644
--- a/ui/resources/resources.qrc
+++ b/ui/resources/resources.qrc
@@ -20,7 +20,9 @@
lang/zh_CN.qm
lang/en_US.qm
- themes/default.json
- themes/tempest.json
+ themes/m3-dynamic_default.json
+ themes/m3-dynamic_tempest.json
+ themes/m3-dynamic_devilun.json
+ themes/m3-dynamic_kupya.json
diff --git a/ui/resources/themes/default.json b/ui/resources/themes/default.json
deleted file mode 100644
index ea75394..0000000
--- a/ui/resources/themes/default.json
+++ /dev/null
@@ -1,427 +0,0 @@
-{
- "//url": "http://material-foundation.github.io/material-theme-builder/?primary=%234E486C&custom%3ASuccess=%2300C555&colorMatch=true",
- "name": "default",
- "description": "TYPE: CUSTOM\nMaterial Theme Builder export",
- "seed": "#4E486C",
- "coreColors": {
- "primary": "#4E486C"
- },
- "extendedColors": [
- {
- "name": "Success",
- "color": "#00C555",
- "description": "",
- "harmonized": true
- }
- ],
- "schemes": {
- "light": {
- "primary": "#373154",
- "surfaceTint": "#605A7F",
- "onPrimary": "#FFFFFF",
- "primaryContainer": "#4E486C",
- "onPrimaryContainer": "#C0B8E3",
- "secondary": "#605C6C",
- "onSecondary": "#FFFFFF",
- "secondaryContainer": "#E3DDF0",
- "onSecondaryContainer": "#646071",
- "tertiary": "#4F2B40",
- "onTertiary": "#FFFFFF",
- "tertiaryContainer": "#684157",
- "onTertiaryContainer": "#E3B0CA",
- "error": "#BA1A1A",
- "onError": "#FFFFFF",
- "errorContainer": "#FFDAD6",
- "onErrorContainer": "#93000A",
- "background": "#FDF8FC",
- "onBackground": "#1C1B1E",
- "surface": "#FDF8FC",
- "onSurface": "#1C1B1E",
- "surfaceVariant": "#E6E1EA",
- "onSurfaceVariant": "#48464D",
- "outline": "#79767E",
- "outlineVariant": "#C9C5CE",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#313033",
- "inverseOnSurface": "#F4EFF3",
- "inversePrimary": "#C9C1EC",
- "primaryFixed": "#E6DEFF",
- "onPrimaryFixed": "#1C1738",
- "primaryFixedDim": "#C9C1EC",
- "onPrimaryFixedVariant": "#484266",
- "secondaryFixed": "#E6E0F3",
- "onSecondaryFixed": "#1C1A27",
- "secondaryFixedDim": "#C9C4D7",
- "onSecondaryFixedVariant": "#484554",
- "tertiaryFixed": "#FFD8EA",
- "onTertiaryFixed": "#301024",
- "tertiaryFixedDim": "#ECB8D2",
- "onTertiaryFixedVariant": "#613B51",
- "surfaceDim": "#DDD9DC",
- "surfaceBright": "#FDF8FC",
- "surfaceContainerLowest": "#FFFFFF",
- "surfaceContainerLow": "#F7F2F6",
- "surfaceContainer": "#F1ECF0",
- "surfaceContainerHigh": "#EBE7EA",
- "surfaceContainerHighest": "#E6E1E5"
- },
- "light-medium-contrast": {
- "primary": "#373154",
- "surfaceTint": "#605A7F",
- "onPrimary": "#FFFFFF",
- "primaryContainer": "#4E486C",
- "onPrimaryContainer": "#EDE7FF",
- "secondary": "#373443",
- "onSecondary": "#FFFFFF",
- "secondaryContainer": "#6F6B7B",
- "onSecondaryContainer": "#FFFFFF",
- "tertiary": "#4E2B40",
- "onTertiary": "#FFFFFF",
- "tertiaryContainer": "#684157",
- "onTertiaryContainer": "#FFE4EF",
- "error": "#740006",
- "onError": "#FFFFFF",
- "errorContainer": "#CF2C27",
- "onErrorContainer": "#FFFFFF",
- "background": "#FDF8FC",
- "onBackground": "#1C1B1E",
- "surface": "#FDF8FC",
- "onSurface": "#121113",
- "surfaceVariant": "#E6E1EA",
- "onSurfaceVariant": "#37353D",
- "outline": "#545159",
- "outlineVariant": "#6F6C74",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#313033",
- "inverseOnSurface": "#F4EFF3",
- "inversePrimary": "#C9C1EC",
- "primaryFixed": "#6F688E",
- "onPrimaryFixed": "#FFFFFF",
- "primaryFixedDim": "#565075",
- "onPrimaryFixedVariant": "#FFFFFF",
- "secondaryFixed": "#6F6B7B",
- "onSecondaryFixed": "#FFFFFF",
- "secondaryFixedDim": "#565363",
- "onSecondaryFixedVariant": "#FFFFFF",
- "tertiaryFixed": "#8B6078",
- "onTertiaryFixed": "#FFFFFF",
- "tertiaryFixedDim": "#71495F",
- "onTertiaryFixedVariant": "#FFFFFF",
- "surfaceDim": "#C9C5C9",
- "surfaceBright": "#FDF8FC",
- "surfaceContainerLowest": "#FFFFFF",
- "surfaceContainerLow": "#F7F2F6",
- "surfaceContainer": "#EBE7EA",
- "surfaceContainerHigh": "#E0DCDF",
- "surfaceContainerHighest": "#D4D0D4"
- },
- "light-high-contrast": {
- "primary": "#2D2749",
- "surfaceTint": "#605A7F",
- "onPrimary": "#FFFFFF",
- "primaryContainer": "#4A4468",
- "onPrimaryContainer": "#FFFFFF",
- "secondary": "#2D2A39",
- "onSecondary": "#FFFFFF",
- "secondaryContainer": "#4A4757",
- "onSecondaryContainer": "#FFFFFF",
- "tertiary": "#432135",
- "onTertiary": "#FFFFFF",
- "tertiaryContainer": "#643D53",
- "onTertiaryContainer": "#FFFFFF",
- "error": "#600004",
- "onError": "#FFFFFF",
- "errorContainer": "#98000A",
- "onErrorContainer": "#FFFFFF",
- "background": "#FDF8FC",
- "onBackground": "#1C1B1E",
- "surface": "#FDF8FC",
- "onSurface": "#000000",
- "surfaceVariant": "#E6E1EA",
- "onSurfaceVariant": "#000000",
- "outline": "#2D2B32",
- "outlineVariant": "#4A4850",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#313033",
- "inverseOnSurface": "#FFFFFF",
- "inversePrimary": "#C9C1EC",
- "primaryFixed": "#4A4468",
- "onPrimaryFixed": "#FFFFFF",
- "primaryFixedDim": "#342E50",
- "onPrimaryFixedVariant": "#FFFFFF",
- "secondaryFixed": "#4A4757",
- "onSecondaryFixed": "#FFFFFF",
- "secondaryFixedDim": "#34313F",
- "onSecondaryFixedVariant": "#FFFFFF",
- "tertiaryFixed": "#643D53",
- "onTertiaryFixed": "#FFFFFF",
- "tertiaryFixedDim": "#4A273C",
- "onTertiaryFixedVariant": "#FFFFFF",
- "surfaceDim": "#BBB8BB",
- "surfaceBright": "#FDF8FC",
- "surfaceContainerLowest": "#FFFFFF",
- "surfaceContainerLow": "#F4EFF3",
- "surfaceContainer": "#E6E1E5",
- "surfaceContainerHigh": "#D7D3D7",
- "surfaceContainerHighest": "#C9C5C9"
- },
- "dark": {
- "primary": "#C9C1EC",
- "surfaceTint": "#C9C1EC",
- "onPrimary": "#312C4E",
- "primaryContainer": "#4E486C",
- "onPrimaryContainer": "#C0B8E3",
- "secondary": "#C9C4D7",
- "onSecondary": "#312E3D",
- "secondaryContainer": "#4A4757",
- "onSecondaryContainer": "#BBB6C8",
- "tertiary": "#ECB8D2",
- "onTertiary": "#48253A",
- "tertiaryContainer": "#684157",
- "onTertiaryContainer": "#E3B0CA",
- "error": "#FFB4AB",
- "onError": "#690005",
- "errorContainer": "#93000A",
- "onErrorContainer": "#FFDAD6",
- "background": "#141315",
- "onBackground": "#E6E1E5",
- "surface": "#141315",
- "onSurface": "#E6E1E5",
- "surfaceVariant": "#48464D",
- "onSurfaceVariant": "#C9C5CE",
- "outline": "#938F98",
- "outlineVariant": "#48464D",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#E6E1E5",
- "inverseOnSurface": "#313033",
- "inversePrimary": "#605A7F",
- "primaryFixed": "#E6DEFF",
- "onPrimaryFixed": "#1C1738",
- "primaryFixedDim": "#C9C1EC",
- "onPrimaryFixedVariant": "#484266",
- "secondaryFixed": "#E6E0F3",
- "onSecondaryFixed": "#1C1A27",
- "secondaryFixedDim": "#C9C4D7",
- "onSecondaryFixedVariant": "#484554",
- "tertiaryFixed": "#FFD8EA",
- "onTertiaryFixed": "#301024",
- "tertiaryFixedDim": "#ECB8D2",
- "onTertiaryFixedVariant": "#613B51",
- "surfaceDim": "#141315",
- "surfaceBright": "#3A393B",
- "surfaceContainerLowest": "#0F0E10",
- "surfaceContainerLow": "#1C1B1E",
- "surfaceContainer": "#201F22",
- "surfaceContainerHigh": "#2B292C",
- "surfaceContainerHighest": "#363437"
- },
- "dark-medium-contrast": {
- "primary": "#E0D7FF",
- "surfaceTint": "#C9C1EC",
- "onPrimary": "#262142",
- "primaryContainer": "#938CB4",
- "onPrimaryContainer": "#000000",
- "secondary": "#DFD9ED",
- "onSecondary": "#262432",
- "secondaryContainer": "#938EA0",
- "onSecondaryContainer": "#000000",
- "tertiary": "#FFCFE7",
- "onTertiary": "#3C1A2E",
- "tertiaryContainer": "#B2839C",
- "onTertiaryContainer": "#000000",
- "error": "#FFD2CC",
- "onError": "#540003",
- "errorContainer": "#FF5449",
- "onErrorContainer": "#000000",
- "background": "#141315",
- "onBackground": "#E6E1E5",
- "surface": "#141315",
- "onSurface": "#FFFFFF",
- "surfaceVariant": "#48464D",
- "onSurfaceVariant": "#DFDAE4",
- "outline": "#B4B0BA",
- "outlineVariant": "#928F98",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#E6E1E5",
- "inverseOnSurface": "#2B292C",
- "inversePrimary": "#494367",
- "primaryFixed": "#E6DEFF",
- "onPrimaryFixed": "#120C2D",
- "primaryFixedDim": "#C9C1EC",
- "onPrimaryFixedVariant": "#373254",
- "secondaryFixed": "#E6E0F3",
- "onSecondaryFixed": "#120F1D",
- "secondaryFixedDim": "#C9C4D7",
- "onSecondaryFixedVariant": "#373443",
- "tertiaryFixed": "#FFD8EA",
- "onTertiaryFixed": "#230619",
- "tertiaryFixedDim": "#ECB8D2",
- "onTertiaryFixedVariant": "#4E2B40",
- "surfaceDim": "#141315",
- "surfaceBright": "#454447",
- "surfaceContainerLowest": "#080709",
- "surfaceContainerLow": "#1E1D20",
- "surfaceContainer": "#28272A",
- "surfaceContainerHigh": "#333235",
- "surfaceContainerHighest": "#3F3D40"
- },
- "dark-high-contrast": {
- "primary": "#F3EDFF",
- "surfaceTint": "#C9C1EC",
- "onPrimary": "#000000",
- "primaryContainer": "#C6BDE8",
- "onPrimaryContainer": "#0B0627",
- "secondary": "#F3EDFF",
- "onSecondary": "#000000",
- "secondaryContainer": "#C5C0D3",
- "onSecondaryContainer": "#0C0916",
- "tertiary": "#FFEBF3",
- "onTertiary": "#000000",
- "tertiaryContainer": "#E7B4CE",
- "onTertiaryContainer": "#1C0213",
- "error": "#FFECE9",
- "onError": "#000000",
- "errorContainer": "#FFAEA4",
- "onErrorContainer": "#220001",
- "background": "#141315",
- "onBackground": "#E6E1E5",
- "surface": "#141315",
- "onSurface": "#FFFFFF",
- "surfaceVariant": "#48464D",
- "onSurfaceVariant": "#FFFFFF",
- "outline": "#F3EEF8",
- "outlineVariant": "#C5C1CA",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#E6E1E5",
- "inverseOnSurface": "#000000",
- "inversePrimary": "#494367",
- "primaryFixed": "#E6DEFF",
- "onPrimaryFixed": "#000000",
- "primaryFixedDim": "#C9C1EC",
- "onPrimaryFixedVariant": "#120C2D",
- "secondaryFixed": "#E6E0F3",
- "onSecondaryFixed": "#000000",
- "secondaryFixedDim": "#C9C4D7",
- "onSecondaryFixedVariant": "#120F1D",
- "tertiaryFixed": "#FFD8EA",
- "onTertiaryFixed": "#000000",
- "tertiaryFixedDim": "#ECB8D2",
- "onTertiaryFixedVariant": "#230619",
- "surfaceDim": "#141315",
- "surfaceBright": "#514F52",
- "surfaceContainerLowest": "#000000",
- "surfaceContainerLow": "#201F22",
- "surfaceContainer": "#313033",
- "surfaceContainerHigh": "#3C3B3E",
- "surfaceContainerHighest": "#484649"
- }
- },
- "palettes": {
- "primary": {
- "0": "#000000",
- "5": "#110B2C",
- "10": "#1C1738",
- "15": "#272142",
- "20": "#312C4E",
- "25": "#3C375A",
- "30": "#484266",
- "35": "#544E72",
- "40": "#605A7F",
- "50": "#797299",
- "60": "#938CB4",
- "70": "#AEA6CF",
- "80": "#C9C1EC",
- "90": "#E6DEFF",
- "95": "#F4EEFF",
- "98": "#FDF8FF",
- "99": "#FFFBFF",
- "100": "#FFFFFF"
- },
- "secondary": {
- "0": "#000000",
- "5": "#111018",
- "10": "#1C1A23",
- "15": "#26252D",
- "20": "#312F38",
- "25": "#3C3A43",
- "30": "#48454F",
- "35": "#54515B",
- "40": "#605D67",
- "50": "#797580",
- "60": "#938F9A",
- "70": "#AEA9B5",
- "80": "#C9C4D0",
- "90": "#E6E0EC",
- "95": "#F4EEFB",
- "98": "#FDF8FF",
- "99": "#FFFBFF",
- "100": "#FFFFFF"
- },
- "tertiary": {
- "0": "#000000",
- "5": "#1B0C13",
- "10": "#27171E",
- "15": "#322128",
- "20": "#3E2B33",
- "25": "#49363E",
- "30": "#564149",
- "35": "#624D55",
- "40": "#6F5861",
- "50": "#897179",
- "60": "#A38A93",
- "70": "#BFA4AD",
- "80": "#DCBFC9",
- "90": "#F9DBE5",
- "95": "#FFECF1",
- "98": "#FFF8F8",
- "99": "#FFFBFF",
- "100": "#FFFFFF"
- },
- "neutral": {
- "0": "#000000",
- "5": "#111112",
- "10": "#1C1B1D",
- "15": "#262527",
- "20": "#313032",
- "25": "#3C3B3D",
- "30": "#484648",
- "35": "#545254",
- "40": "#605E60",
- "50": "#797678",
- "60": "#939092",
- "70": "#ADAAAC",
- "80": "#C9C5C7",
- "90": "#E5E1E3",
- "95": "#F4EFF1",
- "98": "#FDF8FA",
- "99": "#FFFBFF",
- "100": "#FFFFFF"
- },
- "neutral-variant": {
- "0": "#000000",
- "5": "#111014",
- "10": "#1C1B1F",
- "15": "#262529",
- "20": "#313034",
- "25": "#3C3B3F",
- "30": "#48464A",
- "35": "#545256",
- "40": "#605D62",
- "50": "#79767B",
- "60": "#939094",
- "70": "#AEAAAF",
- "80": "#C9C5CA",
- "90": "#E6E1E6",
- "95": "#F4EFF4",
- "98": "#FDF8FD",
- "99": "#FFFBFF",
- "100": "#FFFFFF"
- }
- }
-}
diff --git a/ui/resources/themes/m3-dynamic_default.json b/ui/resources/themes/m3-dynamic_default.json
new file mode 100644
index 0000000..206787d
--- /dev/null
+++ b/ui/resources/themes/m3-dynamic_default.json
@@ -0,0 +1,8 @@
+{
+ "id": "default",
+ "name": "Default",
+ "colors": {
+ "primary": "#4e486c"
+ },
+ "options": {}
+}
diff --git a/ui/resources/themes/m3-dynamic_devilun.json b/ui/resources/themes/m3-dynamic_devilun.json
new file mode 100644
index 0000000..7438c55
--- /dev/null
+++ b/ui/resources/themes/m3-dynamic_devilun.json
@@ -0,0 +1,11 @@
+{
+ "id": "devilun",
+ "name": "Devilun",
+ "colors": {
+ "primary": "#d1779d",
+ "secondary": "#85a9a5"
+ },
+ "options": {
+ "forceScheme": "dark"
+ }
+}
diff --git a/ui/resources/themes/m3-dynamic_kupya.json b/ui/resources/themes/m3-dynamic_kupya.json
new file mode 100644
index 0000000..36ce192
--- /dev/null
+++ b/ui/resources/themes/m3-dynamic_kupya.json
@@ -0,0 +1,10 @@
+{
+ "id": "kupya",
+ "name": "Kupya",
+ "colors": {
+ "primary": "#ffeb9e"
+ },
+ "options": {
+ "forceScheme": "light"
+ }
+}
diff --git a/ui/resources/themes/m3-dynamic_tempest.json b/ui/resources/themes/m3-dynamic_tempest.json
new file mode 100644
index 0000000..00f893b
--- /dev/null
+++ b/ui/resources/themes/m3-dynamic_tempest.json
@@ -0,0 +1,8 @@
+{
+ "id": "tempest",
+ "name": "Tempest",
+ "colors": {
+ "primary": "#186d98"
+ },
+ "options": {}
+}
diff --git a/ui/resources/themes/tempest.json b/ui/resources/themes/tempest.json
deleted file mode 100644
index 9fdbbe8..0000000
--- a/ui/resources/themes/tempest.json
+++ /dev/null
@@ -1,427 +0,0 @@
-{
- "//url": "http://material-foundation.github.io/material-theme-builder/?primary=%23186D98&custom%3ASuccess=%2300C555&colorMatch=true",
- "name": "tempest",
- "description": "TYPE: CUSTOM\nMaterial Theme Builder export",
- "seed": "#186D98",
- "coreColors": {
- "primary": "#186D98"
- },
- "extendedColors": [
- {
- "name": "Success",
- "color": "#00C555",
- "description": "",
- "harmonized": true
- }
- ],
- "schemes": {
- "light": {
- "primary": "#005479",
- "surfaceTint": "#03658F",
- "onPrimary": "#FFFFFF",
- "primaryContainer": "#186D98",
- "onPrimaryContainer": "#CEE9FF",
- "secondary": "#496173",
- "onSecondary": "#FFFFFF",
- "secondaryContainer": "#C9E3F8",
- "onSecondaryContainer": "#4D6678",
- "tertiary": "#683D7A",
- "onTertiary": "#FFFFFF",
- "tertiaryContainer": "#825594",
- "onTertiaryContainer": "#F9DCFF",
- "error": "#BA1A1A",
- "onError": "#FFFFFF",
- "errorContainer": "#FFDAD6",
- "onErrorContainer": "#93000A",
- "background": "#F7F9FD",
- "onBackground": "#191C1F",
- "surface": "#F7F9FD",
- "onSurface": "#191C1F",
- "surfaceVariant": "#DCE3EB",
- "onSurfaceVariant": "#40484E",
- "outline": "#70787F",
- "outlineVariant": "#C0C7CF",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#2D3134",
- "inverseOnSurface": "#EFF1F5",
- "inversePrimary": "#88CEFE",
- "primaryFixed": "#C8E6FF",
- "onPrimaryFixed": "#001E2E",
- "primaryFixedDim": "#88CEFE",
- "onPrimaryFixedVariant": "#004C6D",
- "secondaryFixed": "#CCE6FB",
- "onSecondaryFixed": "#021E2D",
- "secondaryFixedDim": "#B0CADF",
- "onSecondaryFixedVariant": "#314A5B",
- "tertiaryFixed": "#F8D8FF",
- "onTertiaryFixed": "#300443",
- "tertiaryFixedDim": "#E8B4FA",
- "onTertiaryFixedVariant": "#603572",
- "surfaceDim": "#D8DADE",
- "surfaceBright": "#F7F9FD",
- "surfaceContainerLowest": "#FFFFFF",
- "surfaceContainerLow": "#F2F4F7",
- "surfaceContainer": "#ECEEF2",
- "surfaceContainerHigh": "#E6E8EC",
- "surfaceContainerHighest": "#E0E2E6"
- },
- "light-medium-contrast": {
- "primary": "#003A55",
- "surfaceTint": "#03658F",
- "onPrimary": "#FFFFFF",
- "primaryContainer": "#186D98",
- "onPrimaryContainer": "#FFFFFF",
- "secondary": "#203949",
- "onSecondary": "#FFFFFF",
- "secondaryContainer": "#577082",
- "onSecondaryContainer": "#FFFFFF",
- "tertiary": "#4E2460",
- "onTertiary": "#FFFFFF",
- "tertiaryContainer": "#825594",
- "onTertiaryContainer": "#FFFFFF",
- "error": "#740006",
- "onError": "#FFFFFF",
- "errorContainer": "#CF2C27",
- "onErrorContainer": "#FFFFFF",
- "background": "#F7F9FD",
- "onBackground": "#191C1F",
- "surface": "#F7F9FD",
- "onSurface": "#0E1214",
- "surfaceVariant": "#DCE3EB",
- "onSurfaceVariant": "#2F373D",
- "outline": "#4C535A",
- "outlineVariant": "#666E75",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#2D3134",
- "inverseOnSurface": "#EFF1F5",
- "inversePrimary": "#88CEFE",
- "primaryFixed": "#23749F",
- "onPrimaryFixed": "#FFFFFF",
- "primaryFixedDim": "#005B82",
- "onPrimaryFixedVariant": "#FFFFFF",
- "secondaryFixed": "#577082",
- "onSecondaryFixed": "#FFFFFF",
- "secondaryFixedDim": "#3F5869",
- "onSecondaryFixedVariant": "#FFFFFF",
- "tertiaryFixed": "#895C9B",
- "onTertiaryFixed": "#FFFFFF",
- "tertiaryFixedDim": "#6F4381",
- "onTertiaryFixedVariant": "#FFFFFF",
- "surfaceDim": "#C4C7CA",
- "surfaceBright": "#F7F9FD",
- "surfaceContainerLowest": "#FFFFFF",
- "surfaceContainerLow": "#F2F4F7",
- "surfaceContainer": "#E6E8EC",
- "surfaceContainerHigh": "#DBDDE1",
- "surfaceContainerHighest": "#CFD2D5"
- },
- "light-high-contrast": {
- "primary": "#003046",
- "surfaceTint": "#03658F",
- "onPrimary": "#FFFFFF",
- "primaryContainer": "#004E71",
- "onPrimaryContainer": "#FFFFFF",
- "secondary": "#152F3F",
- "onSecondary": "#FFFFFF",
- "secondaryContainer": "#344C5D",
- "onSecondaryContainer": "#FFFFFF",
- "tertiary": "#421955",
- "onTertiary": "#FFFFFF",
- "tertiaryContainer": "#623874",
- "onTertiaryContainer": "#FFFFFF",
- "error": "#600004",
- "onError": "#FFFFFF",
- "errorContainer": "#98000A",
- "onErrorContainer": "#FFFFFF",
- "background": "#F7F9FD",
- "onBackground": "#191C1F",
- "surface": "#F7F9FD",
- "onSurface": "#000000",
- "surfaceVariant": "#DCE3EB",
- "onSurfaceVariant": "#000000",
- "outline": "#252D33",
- "outlineVariant": "#424A51",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#2D3134",
- "inverseOnSurface": "#FFFFFF",
- "inversePrimary": "#88CEFE",
- "primaryFixed": "#004E71",
- "onPrimaryFixed": "#FFFFFF",
- "primaryFixedDim": "#003650",
- "onPrimaryFixedVariant": "#FFFFFF",
- "secondaryFixed": "#344C5D",
- "onSecondaryFixed": "#FFFFFF",
- "secondaryFixedDim": "#1C3546",
- "onSecondaryFixedVariant": "#FFFFFF",
- "tertiaryFixed": "#623874",
- "onTertiaryFixed": "#FFFFFF",
- "tertiaryFixedDim": "#4A205C",
- "onTertiaryFixedVariant": "#FFFFFF",
- "surfaceDim": "#B6B9BD",
- "surfaceBright": "#F7F9FD",
- "surfaceContainerLowest": "#FFFFFF",
- "surfaceContainerLow": "#EFF1F5",
- "surfaceContainer": "#E0E2E6",
- "surfaceContainerHigh": "#D2D4D8",
- "surfaceContainerHighest": "#C4C7CA"
- },
- "dark": {
- "primary": "#88CEFE",
- "surfaceTint": "#88CEFE",
- "onPrimary": "#00344D",
- "primaryContainer": "#186D98",
- "onPrimaryContainer": "#CEE9FF",
- "secondary": "#B0CADF",
- "onSecondary": "#1A3343",
- "secondaryContainer": "#314A5B",
- "onSecondaryContainer": "#9FB8CD",
- "tertiary": "#E8B4FA",
- "onTertiary": "#471E59",
- "tertiaryContainer": "#825594",
- "onTertiaryContainer": "#F9DCFF",
- "error": "#FFB4AB",
- "onError": "#690005",
- "errorContainer": "#93000A",
- "onErrorContainer": "#FFDAD6",
- "background": "#101417",
- "onBackground": "#E0E2E6",
- "surface": "#101417",
- "onSurface": "#E0E2E6",
- "surfaceVariant": "#40484E",
- "onSurfaceVariant": "#C0C7CF",
- "outline": "#8A9299",
- "outlineVariant": "#40484E",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#E0E2E6",
- "inverseOnSurface": "#2D3134",
- "inversePrimary": "#03658F",
- "primaryFixed": "#C8E6FF",
- "onPrimaryFixed": "#001E2E",
- "primaryFixedDim": "#88CEFE",
- "onPrimaryFixedVariant": "#004C6D",
- "secondaryFixed": "#CCE6FB",
- "onSecondaryFixed": "#021E2D",
- "secondaryFixedDim": "#B0CADF",
- "onSecondaryFixedVariant": "#314A5B",
- "tertiaryFixed": "#F8D8FF",
- "onTertiaryFixed": "#300443",
- "tertiaryFixedDim": "#E8B4FA",
- "onTertiaryFixedVariant": "#603572",
- "surfaceDim": "#101417",
- "surfaceBright": "#363A3D",
- "surfaceContainerLowest": "#0B0F11",
- "surfaceContainerLow": "#191C1F",
- "surfaceContainer": "#1D2023",
- "surfaceContainerHigh": "#272A2D",
- "surfaceContainerHighest": "#323538"
- },
- "dark-medium-contrast": {
- "primary": "#BBE1FF",
- "surfaceTint": "#88CEFE",
- "onPrimary": "#00293D",
- "primaryContainer": "#5098C5",
- "onPrimaryContainer": "#000000",
- "secondary": "#C6E0F5",
- "onSecondary": "#0D2838",
- "secondaryContainer": "#7B94A7",
- "onSecondaryContainer": "#000000",
- "tertiary": "#F5D0FF",
- "onTertiary": "#3B114E",
- "tertiaryContainer": "#AF7FC1",
- "onTertiaryContainer": "#000000",
- "error": "#FFD2CC",
- "onError": "#540003",
- "errorContainer": "#FF5449",
- "onErrorContainer": "#000000",
- "background": "#101417",
- "onBackground": "#E0E2E6",
- "surface": "#101417",
- "onSurface": "#FFFFFF",
- "surfaceVariant": "#40484E",
- "onSurfaceVariant": "#D6DDE5",
- "outline": "#ABB3BB",
- "outlineVariant": "#899199",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#E0E2E6",
- "inverseOnSurface": "#272A2D",
- "inversePrimary": "#004D6F",
- "primaryFixed": "#C8E6FF",
- "onPrimaryFixed": "#00131F",
- "primaryFixedDim": "#88CEFE",
- "onPrimaryFixedVariant": "#003A55",
- "secondaryFixed": "#CCE6FB",
- "onSecondaryFixed": "#00131F",
- "secondaryFixedDim": "#B0CADF",
- "onSecondaryFixedVariant": "#203949",
- "tertiaryFixed": "#F8D8FF",
- "onTertiaryFixed": "#220032",
- "tertiaryFixedDim": "#E8B4FA",
- "onTertiaryFixedVariant": "#4E2460",
- "surfaceDim": "#101417",
- "surfaceBright": "#424548",
- "surfaceContainerLowest": "#05080A",
- "surfaceContainerLow": "#1B1E21",
- "surfaceContainer": "#25282B",
- "surfaceContainerHigh": "#303336",
- "surfaceContainerHighest": "#3B3E41"
- },
- "dark-high-contrast": {
- "primary": "#E3F2FF",
- "surfaceTint": "#88CEFE",
- "onPrimary": "#000000",
- "primaryContainer": "#84CAFA",
- "onPrimaryContainer": "#000D17",
- "secondary": "#E3F2FF",
- "onSecondary": "#000000",
- "secondaryContainer": "#ACC6DB",
- "onSecondaryContainer": "#000D17",
- "tertiary": "#FDEAFF",
- "onTertiary": "#000000",
- "tertiaryContainer": "#E4B0F6",
- "onTertiaryContainer": "#190026",
- "error": "#FFECE9",
- "onError": "#000000",
- "errorContainer": "#FFAEA4",
- "onErrorContainer": "#220001",
- "background": "#101417",
- "onBackground": "#E0E2E6",
- "surface": "#101417",
- "onSurface": "#FFFFFF",
- "surfaceVariant": "#40484E",
- "onSurfaceVariant": "#FFFFFF",
- "outline": "#E9F1F9",
- "outlineVariant": "#BCC3CB",
- "shadow": "#000000",
- "scrim": "#000000",
- "inverseSurface": "#E0E2E6",
- "inverseOnSurface": "#000000",
- "inversePrimary": "#004D6F",
- "primaryFixed": "#C8E6FF",
- "onPrimaryFixed": "#000000",
- "primaryFixedDim": "#88CEFE",
- "onPrimaryFixedVariant": "#00131F",
- "secondaryFixed": "#CCE6FB",
- "onSecondaryFixed": "#000000",
- "secondaryFixedDim": "#B0CADF",
- "onSecondaryFixedVariant": "#00131F",
- "tertiaryFixed": "#F8D8FF",
- "onTertiaryFixed": "#000000",
- "tertiaryFixedDim": "#E8B4FA",
- "onTertiaryFixedVariant": "#220032",
- "surfaceDim": "#101417",
- "surfaceBright": "#4D5054",
- "surfaceContainerLowest": "#000000",
- "surfaceContainerLow": "#1D2023",
- "surfaceContainer": "#2D3134",
- "surfaceContainerHigh": "#383C3F",
- "surfaceContainerHighest": "#44474A"
- }
- },
- "palettes": {
- "primary": {
- "0": "#000000",
- "5": "#00131F",
- "10": "#001E2E",
- "15": "#00293D",
- "20": "#00344D",
- "25": "#00405D",
- "30": "#004C6D",
- "35": "#00587E",
- "40": "#03658F",
- "50": "#317EAA",
- "60": "#5098C5",
- "70": "#6CB3E1",
- "80": "#88CEFE",
- "90": "#C8E6FF",
- "95": "#E5F2FF",
- "98": "#F6FAFF",
- "99": "#FBFCFF",
- "100": "#FFFFFF"
- },
- "secondary": {
- "0": "#000000",
- "5": "#05121B",
- "10": "#0F1D26",
- "15": "#1A2731",
- "20": "#25323C",
- "25": "#303D47",
- "30": "#3B4853",
- "35": "#46545F",
- "40": "#52606B",
- "50": "#6B7984",
- "60": "#84929E",
- "70": "#9FADB9",
- "80": "#BAC8D5",
- "90": "#D6E4F2",
- "95": "#E5F2FF",
- "98": "#F6FAFF",
- "99": "#FBFCFF",
- "100": "#FFFFFF"
- },
- "tertiary": {
- "0": "#000000",
- "5": "#140D25",
- "10": "#1E1730",
- "15": "#29223B",
- "20": "#342C46",
- "25": "#3F3752",
- "30": "#4B425E",
- "35": "#564E6A",
- "40": "#635A76",
- "50": "#7C7290",
- "60": "#968CAB",
- "70": "#B1A6C6",
- "80": "#CDC1E2",
- "90": "#E9DDFF",
- "95": "#F6EDFF",
- "98": "#FEF7FF",
- "99": "#FFFBFF",
- "100": "#FFFFFF"
- },
- "neutral": {
- "0": "#000000",
- "5": "#0F1113",
- "10": "#1A1C1E",
- "15": "#242628",
- "20": "#2F3132",
- "25": "#3A3B3D",
- "30": "#454749",
- "35": "#515254",
- "40": "#5D5E60",
- "50": "#767779",
- "60": "#909193",
- "70": "#AAABAD",
- "80": "#C6C6C8",
- "90": "#E2E2E4",
- "95": "#F1F0F3",
- "98": "#F9F9FB",
- "99": "#FCFCFE",
- "100": "#FFFFFF"
- },
- "neutral-variant": {
- "0": "#000000",
- "5": "#0C1215",
- "10": "#171C20",
- "15": "#21262B",
- "20": "#2C3135",
- "25": "#373C40",
- "30": "#42474C",
- "35": "#4E5358",
- "40": "#5A5F64",
- "50": "#73787D",
- "60": "#8C9196",
- "70": "#A7ACB1",
- "80": "#C2C7CC",
- "90": "#DEE3E8",
- "95": "#EDF1F7",
- "98": "#F6FAFF",
- "99": "#FBFCFF",
- "100": "#FFFFFF"
- }
- }
-}
diff --git a/ui/theme/manager.py b/ui/theme/manager.py
index c7dd819..05bc181 100644
--- a/ui/theme/manager.py
+++ b/ui/theme/manager.py
@@ -4,17 +4,17 @@ from typing import overload
import structlog
from PySide6.QtCore import Property, QObject, QResource, Qt, Signal
-from PySide6.QtGui import QColor, QGuiApplication, QPalette
+from PySide6.QtGui import QGuiApplication, QPalette
from .material3 import Material3DynamicThemeImpl, Material3ThemeImpl
from .qml import ThemeQmlExposer
-from .shared import ThemeImpl, ThemeInfo, _TCustomPalette, _TScheme
+from .shared import ThemeImpl, ThemeInfo, TThemeInfoCacheKey, _TCustomPalette, _TScheme
QML_IMPORT_NAME = "internal.ui.theme"
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
-_THEME_CACHES: dict[ThemeInfo, ThemeImpl] = {}
+_THEME_CACHES: dict[TThemeInfoCacheKey, ThemeImpl] = {}
logger: structlog.stdlib.BoundLogger = structlog.get_logger()
@@ -27,30 +27,18 @@ class ThemeManager(QObject):
super().__init__(parent)
self._qPalette = QPalette()
- self._customPalette: _TCustomPalette = {
- "primary": QColor.fromString("#616161"),
- "success": QColor.fromString("#616161"),
- "error": QColor.fromString("#616161"),
- }
+ self._customPalette: _TCustomPalette = ThemeImpl.DEFAULT_CUSTOM_PALETTE
- self._lastThemeInfo = ThemeInfo(
- series="material3",
- name="default",
- scheme=self.getCurrentScheme(),
- )
+ self._lastThemeInfo = ThemeImpl().info
self._qmlExposer = ThemeQmlExposer(themeImpl=ThemeImpl())
- self._cacheMaterial3Theme(themeName="default", scheme="light")
- self._cacheMaterial3Theme(themeName="default", scheme="dark")
- self._cacheMaterial3Theme(themeName="tempest", scheme="light")
- self._cacheMaterial3Theme(themeName="tempest", scheme="dark")
+ self._cacheMaterial3DynamicTheme(themeId="default")
+ self._cacheMaterial3DynamicTheme(themeId="tempest")
- self._cacheMaterial3DynamicTheme(themeName="default", scheme="light")
- self._cacheMaterial3DynamicTheme(themeName="default", scheme="dark")
- self._cacheMaterial3DynamicTheme(themeName="tempest", scheme="light")
- self._cacheMaterial3DynamicTheme(themeName="tempest", scheme="dark")
+ self._cacheMaterial3DynamicTheme(themeId="devilun")
+ self._cacheMaterial3DynamicTheme(themeId="kupya")
- self.setTheme("material3-dynamic", "default")
+ self.setTheme("material3-dynamic", "devilun")
def getCurrentScheme(self) -> _TScheme:
qApp: QGuiApplication = QGuiApplication.instance() # pyright: ignore[reportAssignmentType]
@@ -60,52 +48,47 @@ class ThemeManager(QObject):
else "light"
)
- def getMaterial3Theme(self, themeName: str, scheme: _TScheme) -> Material3ThemeImpl:
- themeDataResource = QResource(f":/themes/{themeName}.json")
- if not themeDataResource.isValid():
- raise ValueError(f"Material3 theme {themeName!r} not found")
+ def _loadQResourceJson(self, resourcePath: str):
+ resource = QResource(resourcePath)
+ if not resource.isValid():
+ raise ValueError(f"Resource {resourcePath!r} invalid")
- themeData = json.loads(
- themeDataResource.uncompressedData().data().decode("utf-8")
- )
+ return json.loads(resource.uncompressedData().data().decode("utf-8"))
+ def getMaterial3Theme(self, themeId: str, scheme: _TScheme) -> Material3ThemeImpl:
+ themeData = self._loadQResourceJson(f":/themes/m3_{themeId}.json")
return Material3ThemeImpl(themeData=themeData, scheme=scheme)
def getMaterial3DynamicTheme(
- self, themeName: str, scheme: _TScheme
+ self, themeId: str, scheme: _TScheme
) -> Material3DynamicThemeImpl:
- themeDataResource = QResource(f":/themes/{themeName}.json")
- if not themeDataResource.isValid():
- raise ValueError(f"Material3 theme {themeName!r} not found")
-
- themeData = json.loads(
- themeDataResource.uncompressedData().data().decode("utf-8")
- )
-
- return Material3DynamicThemeImpl(
- sourceColorHex=themeData["seed"],
- scheme=scheme,
- name=themeName,
- )
+ themeData = self._loadQResourceJson(f":/themes/m3-dynamic_{themeId}.json")
+ return Material3DynamicThemeImpl(themeData=themeData, scheme=scheme)
def _cacheTheme(self, *, themeImpl: ThemeImpl):
- _THEME_CACHES[themeImpl.info] = themeImpl
+ _THEME_CACHES[themeImpl.info.cacheKey()] = themeImpl
logger.debug("Theme %r cached", themeImpl.info)
- def _getCachedTheme(self, *, themeInfo: ThemeInfo):
- cachedTheme = _THEME_CACHES.get(themeInfo)
+ def _getCachedTheme(self, *, key: TThemeInfoCacheKey):
+ cachedTheme = _THEME_CACHES.get(key)
if cachedTheme is None:
- raise KeyError(f"Theme {themeInfo!r} not cached")
+ raise KeyError(f"Theme {key!r} not cached")
return cachedTheme
- def _cacheMaterial3Theme(self, *, themeName: str, scheme: _TScheme):
+ def _cacheMaterial3Theme(self, *, themeId: str):
self._cacheTheme(
- themeImpl=self.getMaterial3Theme(themeName=themeName, scheme=scheme),
+ themeImpl=self.getMaterial3Theme(themeId=themeId, scheme="light"),
+ )
+ self._cacheTheme(
+ themeImpl=self.getMaterial3Theme(themeId=themeId, scheme="dark"),
)
- def _cacheMaterial3DynamicTheme(self, *, themeName: str, scheme: _TScheme):
+ def _cacheMaterial3DynamicTheme(self, *, themeId: str):
self._cacheTheme(
- themeImpl=self.getMaterial3DynamicTheme(themeName=themeName, scheme=scheme)
+ themeImpl=self.getMaterial3DynamicTheme(themeId=themeId, scheme="light")
+ )
+ self._cacheTheme(
+ themeImpl=self.getMaterial3DynamicTheme(themeId=themeId, scheme="dark")
)
@overload
@@ -113,29 +96,29 @@ class ThemeManager(QObject):
@overload
def setTheme(
- self, themeSeries: str, themeName: str, scheme: _TScheme | None = None, /
+ self, themeSeries: str, themeId: str, scheme: _TScheme | None = None, /
): ...
def setTheme(self, *args, **kwargs):
if "themeInfo" in kwargs:
- themeInfo = kwargs["themeInfo"]
+ cacheKey = kwargs["themeInfo"].cacheKey()
elif 2 <= len(args) <= 3:
themeSeries = args[0]
- themeName = args[1]
+ themeId = args[1]
schemeArg = args[2] if len(args) > 2 else None
scheme = schemeArg or self.getCurrentScheme()
- themeInfo = ThemeInfo(series=themeSeries, name=themeName, scheme=scheme)
+ cacheKey: TThemeInfoCacheKey = (themeSeries, themeId, scheme)
else:
raise TypeError("Invalid setTheme() call")
- logger.debug("Preparing to set theme %r", themeInfo)
+ logger.debug("Preparing to set theme %r", cacheKey)
- cachedTheme = self._getCachedTheme(themeInfo=themeInfo)
+ cachedTheme = self._getCachedTheme(key=cacheKey)
self._qPalette = cachedTheme.qPalette
self._customPalette = cachedTheme.customPalette
- self._lastThemeInfo = themeInfo
+ self._lastThemeInfo = cachedTheme.info
self._qmlExposer.themeImpl = cachedTheme
self.themeChanged.emit()
diff --git a/ui/theme/material3.py b/ui/theme/material3.py
index 16f58f5..6b41182 100644
--- a/ui/theme/material3.py
+++ b/ui/theme/material3.py
@@ -1,5 +1,3 @@
-from typing import TypedDict
-
from materialyoucolor.blend import Blend
from materialyoucolor.dynamiccolor.contrast_curve import ContrastCurve
from materialyoucolor.dynamiccolor.dynamic_color import DynamicColor, FromPaletteOptions
@@ -10,48 +8,12 @@ from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
from PySide6.QtGui import QColor, QPalette
from .shared import ThemeImpl, ThemeInfo, _TCustomPalette, _TScheme
-
-
-class _M3ThemeDataExtendedColorItem(TypedDict):
- name: str
- color: str
- description: str
- harmonized: bool
-
-
-_M3ThemeDataSchemes = TypedDict(
- "_M3ThemeDataSchemes",
- {
- "light": dict[str, str],
- "light-medium-contrast": dict[str, str],
- "light-high-contrast": dict[str, str],
- "dark": dict[str, str],
- "dark-medium-contrast": dict[str, str],
- "dark-high-contrast": dict[str, str],
- },
+from .types import (
+ TMaterial3DynamicThemeData,
+ TMaterial3ThemeData,
+ TMaterial3ThemeDataExtendedColorItem,
)
-_M3ThemeDataPalettes = TypedDict(
- "_M3ThemeDataPalettes",
- {
- "primary": dict[str, str],
- "secondary": dict[str, str],
- "tertiary": dict[str, str],
- "neutral": dict[str, str],
- "neutral-variant": dict[str, str],
- },
-)
-
-
-class _M3ThemeData(TypedDict):
- name: str
- description: str
- seed: str
- coreColors: dict[str, str]
- extendedColors: list[_M3ThemeDataExtendedColorItem]
- schemes: _M3ThemeDataSchemes
- palettes: _M3ThemeDataPalettes
-
def _hexToHct(hexColor: str) -> Hct:
pureHexPart = hexColor[1:] if hexColor.startswith("#") else hexColor
@@ -87,7 +49,7 @@ class Material3ThemeImpl(ThemeImpl):
QPalette.ColorRole.LinkVisited: "tertiaryContainer",
}
- def __init__(self, *, themeData: _M3ThemeData, scheme: _TScheme):
+ def __init__(self, *, themeData: TMaterial3ThemeData, scheme: _TScheme):
self.themeData = themeData
self.scheme: _TScheme = scheme
@@ -96,7 +58,7 @@ class Material3ThemeImpl(ThemeImpl):
def _findExtendedColor(
self, colorName: str
- ) -> _M3ThemeDataExtendedColorItem | None:
+ ) -> TMaterial3ThemeDataExtendedColorItem | None:
return next(
(it for it in self.themeData["extendedColors"] if it["name"] == colorName),
None,
@@ -106,6 +68,7 @@ class Material3ThemeImpl(ThemeImpl):
def info(self):
return ThemeInfo(
series="material3",
+ id=self.themeData["id"],
name=self.themeData["name"],
scheme=self.scheme,
)
@@ -137,6 +100,8 @@ class Material3ThemeImpl(ThemeImpl):
"primary": _hctToQColor(primaryHct),
"success": _hctToQColor(successHarmonizedHct),
"error": QColor.fromString(self.themeData["schemes"][self.scheme]["error"]),
+ "toolTipBase": self.qPalette.color(QPalette.ColorRole.ToolTipBase),
+ "toolTipText": self.qPalette.color(QPalette.ColorRole.ToolTipText),
}
@@ -169,20 +134,45 @@ class Material3DynamicThemeImpl(ThemeImpl):
"success": "#00c555",
}
- def __init__(self, sourceColorHex: str, scheme: _TScheme, *, name: str):
+ def __init__(self, themeData: TMaterial3DynamicThemeData, scheme: _TScheme):
+ force_scheme = themeData["options"].get("forceScheme")
+ if force_scheme:
+ is_dark = force_scheme == "dark"
+ else:
+ is_dark = scheme == "dark"
+
+ # TODO: more elegant way?
+ self.preferredScheme: _TScheme = scheme # for theme caching
+ self.actualScheme = "dark" if is_dark else "light"
+
+ self.themeId = themeData["id"]
+ self.themeName = themeData["name"]
+
self.material3Scheme = SchemeTonalSpot(
- _hexToHct(sourceColorHex),
- is_dark=scheme == "dark",
+ _hexToHct(themeData["colors"]["primary"]),
+ is_dark=is_dark,
contrast_level=0.0,
)
- self.name = name
+
+ secondary_color = themeData["colors"].get("secondary")
+ if secondary_color:
+ self.material3Scheme.secondary_palette = TonalPalette.from_hct(
+ _hexToHct(secondary_color)
+ )
+
+ tertiary_color = themeData["colors"].get("tertiary")
+ if tertiary_color:
+ self.material3Scheme.tertiary_palette = TonalPalette.from_hct(
+ _hexToHct(tertiary_color)
+ )
@property
def info(self):
return ThemeInfo(
series="material3-dynamic",
- name=self.name,
- scheme="dark" if self.material3Scheme.is_dark else "light",
+ id=self.themeId,
+ name=self.themeName,
+ scheme=self.preferredScheme,
)
@property
@@ -242,4 +232,6 @@ class Material3DynamicThemeImpl(ThemeImpl):
extendedPalettes["success"].get_hct(self.material3Scheme)
),
"error": _hctToQColor(errorHct),
+ "toolTipBase": self.qPalette.color(QPalette.ColorRole.ToolTipBase),
+ "toolTipText": self.qPalette.color(QPalette.ColorRole.ToolTipText),
}
diff --git a/ui/theme/qml.py b/ui/theme/qml.py
index 6209067..05fe455 100644
--- a/ui/theme/qml.py
+++ b/ui/theme/qml.py
@@ -35,3 +35,11 @@ class ThemeQmlExposer(QObject):
@Property(QColor, notify=themeChanged)
def error(self):
return self._themeImpl.customPalette["error"]
+
+ @Property(QColor, notify=themeChanged)
+ def toolTipBase(self):
+ return self._themeImpl.customPalette["toolTipBase"]
+
+ @Property(QColor, notify=themeChanged)
+ def toolTipText(self):
+ return self._themeImpl.customPalette["toolTipText"]
diff --git a/ui/theme/shared.py b/ui/theme/shared.py
index e323dc1..9c471f5 100644
--- a/ui/theme/shared.py
+++ b/ui/theme/shared.py
@@ -9,30 +9,43 @@ class _TCustomPalette(TypedDict):
success: QColor
error: QColor
+ toolTipBase: QColor
+ toolTipText: QColor
+
_TScheme = Literal["light", "dark"]
+TThemeInfoCacheKey = tuple[str, str, _TScheme]
+
@dataclass
class ThemeInfo:
series: str
+ id: str
name: str
scheme: _TScheme
- def __hash__(self) -> int:
- return hash((self.series, self.name, self.scheme))
+ def cacheKey(self) -> TThemeInfoCacheKey:
+ return (self.series, self.id, self.scheme)
class ThemeImpl:
- DEFAULT_CUSTOM_PALETTE = {
+ DEFAULT_CUSTOM_PALETTE: _TCustomPalette = {
"primary": QColor.fromString("#616161"),
"success": QColor.fromString("#616161"),
"error": QColor.fromString("#616161"),
+ "toolTipBase": QColor.fromString("#616161"),
+ "toolTipText": QColor.fromString("#616161"),
}
@property
def info(self) -> ThemeInfo:
- return ThemeInfo(series="placeholder", name="placeholder", scheme="dark")
+ return ThemeInfo(
+ series="placeholder",
+ id="placeholder",
+ name="placeholder",
+ scheme="dark",
+ )
@property
def qPalette(self) -> QPalette:
diff --git a/ui/theme/types.py b/ui/theme/types.py
new file mode 100644
index 0000000..5760bfe
--- /dev/null
+++ b/ui/theme/types.py
@@ -0,0 +1,72 @@
+from typing import TypedDict
+
+from .shared import _TScheme
+
+# region material3
+
+
+class TMaterial3ThemeDataExtendedColorItem(TypedDict):
+ name: str
+ color: str
+ description: str
+ harmonized: bool
+
+
+TMaterial3ThemeDataSchemes = TypedDict(
+ "TMaterial3ThemeDataSchemes",
+ {
+ "light": dict[str, str],
+ "light-medium-contrast": dict[str, str],
+ "light-high-contrast": dict[str, str],
+ "dark": dict[str, str],
+ "dark-medium-contrast": dict[str, str],
+ "dark-high-contrast": dict[str, str],
+ },
+)
+
+TMaterial3ThemeDataPalettes = TypedDict(
+ "TMaterial3ThemeDataPalettes",
+ {
+ "primary": dict[str, str],
+ "secondary": dict[str, str],
+ "tertiary": dict[str, str],
+ "neutral": dict[str, str],
+ "neutral-variant": dict[str, str],
+ },
+)
+
+
+class TMaterial3ThemeData(TypedDict):
+ id: str
+ name: str
+ description: str
+ seed: str
+ coreColors: dict[str, str]
+ extendedColors: list[TMaterial3ThemeDataExtendedColorItem]
+ schemes: TMaterial3ThemeDataSchemes
+ palettes: TMaterial3ThemeDataPalettes
+
+
+# endregion
+
+# region material3-dynamic
+
+
+class TMaterial3DynamicThemeDataColors(TypedDict):
+ primary: str
+ secondary: str | None
+ tertiary: str | None
+
+
+class TMaterial3DynamicThemeDataOptions(TypedDict):
+ forceScheme: _TScheme | None
+
+
+class TMaterial3DynamicThemeData(TypedDict):
+ id: str
+ name: str
+ colors: TMaterial3DynamicThemeDataColors
+ options: TMaterial3DynamicThemeDataOptions
+
+
+# endregion