diff --git a/package-lock.json b/package-lock.json index 4ae8deb995..e8a8dac652 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "dependencies": { "@gtm-support/vue-gtm": "1.2.3", + "@material/material-color-utilities": "0.3.0", "@quasar/extras": "1.10.10", "@sevenc-nanashi/utaformatix-ts": "npm:@jsr/sevenc-nanashi__utaformatix-ts@0.3.2", "async-lock": "1.4.0", @@ -3908,6 +3909,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/@material/material-color-utilities": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.3.0.tgz", + "integrity": "sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==", + "license": "Apache-2.0" + }, "node_modules/@mdx-js/react": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", diff --git a/package.json b/package.json index fe8d183225..92ec4670a4 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ }, "dependencies": { "@gtm-support/vue-gtm": "1.2.3", + "@material/material-color-utilities": "0.3.0", "@quasar/extras": "1.10.10", "@sevenc-nanashi/utaformatix-ts": "npm:@jsr/sevenc-nanashi__utaformatix-ts@0.3.2", "async-lock": "1.4.0", diff --git a/public/color-schemes/default.json b/public/color-schemes/default.json new file mode 100644 index 0000000000..b02a5d0d2c --- /dev/null +++ b/public/color-schemes/default.json @@ -0,0 +1,171 @@ +{ + "name": "デフォルト", + "sourceColor": "#A5D4AD", + "variant": "tonalSpot", + "contrastLevel": 0.0, + "adjustments": { + "neutral": { "chroma": 0 }, + "neutralVariant": { "chroma": 6 }, + "tertiary": { "hex": "#ffc919" }, + "error": { "hex": "#d04756" } + }, + "customPaletteColors": [ + { + "name": "singGridCellWhite", + "palette": "neutral", + "lightTone": 100, + "darkTone": 15, + "blend": true, + "contrastVs": { + "singGridCellBlack": 1.0 + } + }, + { + "name": "singGridCellBlack", + "palette": "neutral", + "lightTone": 98, + "darkTone": 12, + "blend": true, + "contrastVs": { + "singGridCellWhite": 1.0 + } + }, + { + "name": "singRulerMeasureLine", + "palette": "neutralVariant", + "lightTone": 50, + "darkTone": 50, + "blend": true, + "contrastVs": { + "surfaceContainer": 1.5 + } + }, + { + "name": "singRulerBeatLine", + "palette": "neutralVariant", + "lightTone": 70, + "darkTone": 40, + "blend": true, + "contrastVs": { + "surfaceContainerHigh": 1.2 + } + }, + { + "name": "singGridVerticalLine", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true, + "contrastVs": { + "singGridCellBlack": 1.2 + } + }, + { + "name": "singGridHorizontalLine", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true, + "contrastVs": { + "singGridCellBlack": 1.2 + } + }, + { + "name": "singGridMeasureLine", + "palette": "neutral", + "lightTone": 80, + "darkTone": 40, + "blend": true, + "contrastVs": { + "singGridCellBlack": 1.5 + } + }, + { + "name": "singGridBeatLine", + "palette": "neutral", + "lightTone": 90, + "darkTone": 0, + "blend": true, + "contrastVs": { + "singGridCellBlack": 1.2 + } + }, + { + "name": "singGridOctaveLine", + "palette": "neutral", + "lightTone": 80, + "darkTone": 40, + "blend": true, + "contrastVs": { + "singGridCellBlack": 1.5 + } + }, + { + "name": "singPianoKeyWhite", + "palette": "neutral", + "lightTone": 99, + "darkTone": 70, + "blend": true, + "contrastVs": { + "singPianoKeyBlack": 3.0 + } + }, + { + "name": "singPianoKeyBlack", + "palette": "neutral", + "lightTone": 40, + "darkTone": 20, + "blend": true, + "contrastVs": { + "singPianoKeyWhite": 3.0 + } + }, + { + "name": "singNoteBarContainer", + "palette": "secondary", + "lightTone": 90, + "darkTone": 70, + "blend": true, + "contrastVs": { + "singGridCellBlack": 3.0 + } + }, + { + "name": "singNoteBarOutline", + "palette": "neutral", + "lightTone": 40, + "darkTone": 30, + "blend": true, + "contrastVs": { + "singGridCellBlack": 3.0 + } + }, + { + "name": "singToolbarContainer", + "palette": "neutral", + "lightTone": 99, + "darkTone": 10, + "blend": true, + "contrastVs": { + "outline": 1.5 + } + } + ], + "customDefinedColors": [ + { + "name": "primitive-primary", + "value": "#A5D4AD", + "blend": true + }, + { + "name": "primitive-blue", + "value": "#0969da", + "blend": true + }, + { + "name": "primitive-red", + "value": "#d04756", + "blend": true + } + ] +} \ No newline at end of file diff --git a/public/color-schemes/monochrome.json b/public/color-schemes/monochrome.json new file mode 100644 index 0000000000..e25de53f5d --- /dev/null +++ b/public/color-schemes/monochrome.json @@ -0,0 +1,122 @@ +{ + "name": "モノクローム", + "sourceColor": "#A5D4AD", + "variant": "monochrome", + "isDark": false, + "contrastLevel": 0.1, + "adjustments": { + "neutral": { + "chroma": 0 + }, + "neutralVariant": { + "chroma": 6 + }, + "tertiary": { + "hex": "#ffc919" + }, + "error": { + "hex": "#d04756" + } + }, + "customPaletteColors": [ + { + "name": "sing-grid-cell-white", + "palette": "neutral", + "lightTone": 100, + "darkTone": 15, + "blend": true + }, + { + "name": "sing-grid-cell-black", + "palette": "neutral", + "lightTone": 96, + "darkTone": 12, + "blend": true + }, + { + "name": "sing-ruler-beat-line", + "palette": "neutralVariant", + "lightTone": 70, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-ruler-measure-line", + "palette": "neutralVariant", + "lightTone": 50, + "darkTone": 50, + "blend": true + }, + { + "name": "sing-grid-vertical-line", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true + }, + { + "name": "sing-grid-horizontal-line", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true + }, + { + "name": "sing-grid-beat-line", + "palette": "neutral", + "lightTone": 90, + "darkTone": 0, + "blend": true + }, + { + "name": "sing-grid-measure-line", + "palette": "neutral", + "lightTone": 80, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-grid-octave-line", + "palette": "neutral", + "lightTone": 80, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-piano-key-white", + "palette": "neutral", + "lightTone": 99, + "darkTone": 70, + "blend": true + }, + { + "name": "sing-piano-key-black", + "palette": "neutral", + "lightTone": 40, + "darkTone": 20, + "blend": true + }, + { + "name": "sing-note-bar-container", + "palette": "secondary", + "lightTone": 90, + "darkTone": 70, + "blend": true + }, + { + "name": "sing-note-bar-outline", + "palette": "neutral", + "lightTone": 40, + "darkTone": 30, + "blend": true + }, + { + "name": "sing-toolbar-container", + "palette": "neutral", + "lightTone": 100, + "darkTone": 10, + "blend": true + } + ], + "customDefinedColors": [] +} \ No newline at end of file diff --git a/public/color-schemes/none.json b/public/color-schemes/none.json new file mode 100644 index 0000000000..80dba2d117 --- /dev/null +++ b/public/color-schemes/none.json @@ -0,0 +1,124 @@ +{ + "name": "調整なし(M3デフォルト)", + "sourceColor": "#A5D4AD", + "variant": "content", + "contrastLevel": 0.0, + "adjustments": {}, + "customPaletteColors": [ + { + "name": "sing-grid-cell-white", + "palette": "neutral", + "lightTone": 100, + "darkTone": 15, + "blend": true + }, + { + "name": "sing-grid-cell-black", + "palette": "neutral", + "lightTone": 98, + "darkTone": 12, + "blend": true + }, + { + "name": "sing-ruler-beat-line", + "palette": "neutralVariant", + "lightTone": 70, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-ruler-measure-line", + "palette": "neutralVariant", + "lightTone": 50, + "darkTone": 50, + "blend": true + }, + { + "name": "sing-grid-vertical-line", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true + }, + { + "name": "sing-grid-horizontal-line", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true + }, + { + "name": "sing-grid-beat-line", + "palette": "neutral", + "lightTone": 90, + "darkTone": 0, + "blend": true + }, + { + "name": "sing-grid-measure-line", + "palette": "neutral", + "lightTone": 80, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-grid-octave-line", + "palette": "neutral", + "lightTone": 80, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-piano-key-white", + "palette": "neutral", + "lightTone": 99, + "darkTone": 70, + "blend": true + }, + { + "name": "sing-piano-key-black", + "palette": "neutral", + "lightTone": 40, + "darkTone": 20, + "blend": true + }, + { + "name": "sing-note-bar-container", + "palette": "secondary", + "lightTone": 90, + "darkTone": 70, + "blend": true + }, + { + "name": "sing-note-bar-outline", + "palette": "neutral", + "lightTone": 40, + "darkTone": 30, + "blend": true + }, + { + "name": "sing-toolbar-container", + "palette": "neutral", + "lightTone": 99, + "darkTone": 10, + "blend": true + } + ], + "customDefinedColors": [ + { + "name": "primitive-primary", + "value": "#A5D4AD", + "blend": false + }, + { + "name": "primitive-blue", + "value": "#0969da", + "blend": false + }, + { + "name": "primitive-red", + "value": "#d04756", + "blend": false + } + ] +} \ No newline at end of file diff --git a/public/color-schemes/spring.json b/public/color-schemes/spring.json new file mode 100644 index 0000000000..d87eb8b3ca --- /dev/null +++ b/public/color-schemes/spring.json @@ -0,0 +1,134 @@ +{ + "name": "スプリング", + "sourceColor": "#9cf476", + "variant": "content", + "contrastLevel": 0, + "adjustments": { + "neutral": { + "chroma": 0 + }, + "neutralVariant": { + "chroma": 20.1, + "tone": 0 + }, + "tertiary": { + "hex": "#ffae52", + "chroma": 70, + "tone": 80 + }, + "error": { + "hex": "#d04756" + }, + "primary": { + "hex": "#f6ee1e", + "chroma": 125.7, + "tone": 75, + "hue": 100 + }, + "secondary": { + "chroma": 28.6, + "tone": 50 + } + }, + "customPaletteColors": [ + { + "name": "sing-grid-cell-white", + "palette": "neutralVariant", + "lightTone": 96, + "darkTone": 15, + "blend": true + }, + { + "name": "sing-grid-cell-black", + "palette": "neutralVariant", + "lightTone": 93, + "darkTone": 12, + "blend": true + }, + { + "name": "sing-ruler-beat-line", + "palette": "neutralVariant", + "lightTone": 70, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-ruler-measure-line", + "palette": "tertiary", + "lightTone": 80, + "darkTone": 70, + "blend": true + }, + { + "name": "sing-grid-vertical-line", + "palette": "neutral", + "lightTone": 90, + "darkTone": 10, + "blend": true + }, + { + "name": "sing-grid-horizontal-line", + "palette": "neutral", + "lightTone": 95, + "darkTone": 10, + "blend": true + }, + { + "name": "sing-grid-beat-line", + "palette": "neutral", + "lightTone": 90, + "darkTone": 0, + "blend": true + }, + { + "name": "sing-grid-measure-line", + "palette": "tertiary", + "lightTone": 80, + "darkTone": 40, + "blend": true + }, + { + "name": "sing-grid-octave-line", + "palette": "neutral", + "lightTone": 75, + "darkTone": 30, + "blend": true + }, + { + "name": "sing-piano-key-white", + "palette": "neutral", + "lightTone": 99, + "darkTone": 60, + "blend": true + }, + { + "name": "sing-piano-key-black", + "palette": "neutral", + "lightTone": 65, + "darkTone": 20, + "blend": true + }, + { + "name": "sing-note-bar-container", + "palette": "secondary", + "lightTone": 90, + "darkTone": 80, + "blend": true + }, + { + "name": "sing-note-bar-outline", + "palette": "neutral", + "lightTone": 40, + "darkTone": 30, + "blend": true + }, + { + "name": "sing-toolbar-container", + "palette": "neutralVariant", + "lightTone": 100, + "darkTone": 25, + "blend": true + } + ], + "customDefinedColors": [] +} \ No newline at end of file diff --git a/src/backend/browser/sandbox.ts b/src/backend/browser/sandbox.ts index f2f5451085..431bd01763 100644 --- a/src/backend/browser/sandbox.ts +++ b/src/backend/browser/sandbox.ts @@ -19,6 +19,7 @@ import { HotkeySettingType, Sandbox, ThemeConf, + ColorSchemeConfig, } from "@/type/preload"; import { ContactTextFileName, @@ -311,6 +312,32 @@ export const api: Sandbox = { ), ); }, + async getColorSchemeConfigs() { + // ブラウザ版では、ファイルシステムから直接読み込むのではなく、 + // ビルド時に生成されたファイルを読み込む + // NOTE: 定数にする? + const colorSchemeFiles = ["default.json", "none.json", "monochrome.json"]; + + // カラースキーム個別の取得 + const fetchColorScheme = async ( + fileName: string, + ): Promise => { + const response = await fetch(`/color-schemes/${fileName}`); + if (!response.ok) { + throw new Error(`Failed to load color scheme: ${fileName}`); + } + return response.json(); + }; + + // すべてのカラースキームの取得 + return Promise.all(colorSchemeFiles.map(fetchColorScheme)) + .then((colorSchemes) => { + return colorSchemes as ColorSchemeConfig[]; + }) + .catch((error) => { + throw new Error(`Error loading color schemes: ${error}`); + }); + }, vuexReady() { // NOTE: 何もしなくて良さそう return Promise.resolve(); diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index 96b6ff18b4..61ae3400db 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -46,6 +46,7 @@ import { defaultToolbarButtonSetting, engineSettingSchema, EngineId, + ColorSchemeConfig, } from "@/type/preload"; type SingleInstanceLockData = { @@ -324,6 +325,23 @@ function readThemeFiles() { return themes; } +// カラースキームを読み込み +function readColorSchemeConfig(schemeFile: string): ColorSchemeConfig { + const filePath = path.join(__static, "color-schemes", schemeFile); + const fileContent = fs.readFileSync(filePath, "utf-8"); + return JSON.parse(fileContent) as ColorSchemeConfig; +} + +// 利用可能なカラースキームのリストを取得する関数 +function getAvailableColorSchemeConfigs(): string[] { + const schemesDir = path.join(__static, "color-schemes"); + return fs.readdirSync(schemesDir).filter((file) => file.endsWith(".json")); +} + +const colorSchemeConfigs = getAvailableColorSchemeConfigs().map((schemeFile) => + readColorSchemeConfig(schemeFile), +); + // 使い方テキストの読み込み const howToUseText = fs.readFileSync( path.join(__static, HowToUseTextFileName), @@ -933,6 +951,10 @@ ipcMainHandle("THEME", (_, { newData }) => { }; }); +ipcMainHandle("GET_COLOR_SCHEME_CONFIGS", () => { + return colorSchemeConfigs; +}); + ipcMainHandle("ON_VUEX_READY", () => { win.show(); }); diff --git a/src/backend/electron/preload.ts b/src/backend/electron/preload.ts index d529909e2c..1102fafb7b 100644 --- a/src/backend/electron/preload.ts +++ b/src/backend/electron/preload.ts @@ -230,6 +230,10 @@ const api: Sandbox = { return ipcRenderer.invoke("THEME", { newData }); }, + getColorSchemeConfigs: () => { + return ipcRenderer.invoke("GET_COLOR_SCHEME_CONFIGS"); + }, + vuexReady: () => { ipcRenderer.invoke("ON_VUEX_READY"); }, diff --git a/src/components/Dialog/SettingDialog/SettingDialog.vue b/src/components/Dialog/SettingDialog/SettingDialog.vue index 75574e3a90..d3828bd280 100644 --- a/src/components/Dialog/SettingDialog/SettingDialog.vue +++ b/src/components/Dialog/SettingDialog/SettingDialog.vue @@ -513,6 +513,14 @@ ) " /> + @@ -612,6 +620,8 @@ const currentThemeNameComputed = computed({ get: () => store.state.themeSetting.currentTheme, set: (currentTheme: string) => { store.dispatch("SET_THEME_SETTING", { currentTheme: currentTheme }); + // テーマ変更時に色スキームを初期値にする(ライト/ダーク切り替え) + store.dispatch("INITIALIZE_COLOR_SCHEME"); }, }); diff --git a/src/components/Sing/CharacterMenuButton/MenuButton.vue b/src/components/Sing/CharacterMenuButton/MenuButton.vue index 8bc146b6f4..8e0e6927b3 100644 --- a/src/components/Sing/CharacterMenuButton/MenuButton.vue +++ b/src/components/Sing/CharacterMenuButton/MenuButton.vue @@ -243,15 +243,20 @@ const engineIcons = useEngineIcons(() => store.state.engineManifests); @use "@/styles/colors" as colors; .character-menu { + .q-menu { + :deep(.q-menu__container) { + border-radius: 1.25rem; + } + } .q-item { - color: colors.$display; + color: var(--md-sys-color-on-surface); } .q-btn-group { > .q-btn:first-child > :deep(.q-btn__content) { justify-content: flex-start; } > div:last-child:hover { - background-color: rgba(colors.$primary-rgb, 0.1); + background-color: rgba(var(--md-sys-color-secondary-rgb), 0.1); } } .engine-icon { diff --git a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue index b2c2287444..3eb407d7c3 100644 --- a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue +++ b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue @@ -5,8 +5,8 @@
- +
@@ -83,10 +79,22 @@ const selectedStyleIconPath = computed(() => { @use "@/styles/colors" as colors; .selected-character { + border: 1px solid var(--md-sys-color-outline-variant); + border-radius: 4px 0 0 4px; align-items: center; display: flex; - padding: 0.25rem 0.5rem 0.25rem 0.25rem; + padding: 4px; position: relative; + height: 56px; + + &:hover { + border-color: var(--md-sys-color-outline); + background: rgba(var(--md-sys-color-secondary-container-rgb), 0.1); + } + + &:focus { + border-color: var(--md-sys-color-primary); + } .character-avatar-icon { display: block; @@ -99,33 +107,37 @@ const selectedStyleIconPath = computed(() => { align-items: start; display: flex; flex-direction: column; - margin-left: 0.5rem; + margin-left: 8px; text-align: left; justify-content: center; white-space: nowrap; } + .character-name { - font-size: 0.875rem; - font-weight: bold; - line-height: 1rem; - padding-top: 0.5rem; + color: var(--md-sys-color-on-surface); + font-size: 15px; + font-weight: 500; + line-height: 24px; + padding-top: 8px; + margin-bottom: 0; &.skeleton { - margin-top: 0.4rem; - margin-bottom: 0.2rem; + margin-top: 0; + margin-bottom: 8px; } } .character-style { - color: rgba(colors.$display-rgb, 0.6); - font-size: 0.75rem; - font-weight: bold; - line-height: 1rem; + color: var(--md-sys-color-on-surface-variant); + font-size: 10px; + font-weight: 500; + line-height: 16px; + margin-bottom: 8px; } .character-menu-dropdown-icon { - color: rgba(colors.$display-rgb, 0.8); - margin-left: 0.25rem; + color: var(--md-sys-color-on-surface-variant); + margin-left: 4px; } } diff --git a/src/components/Sing/ColorSchemeEditor.vue b/src/components/Sing/ColorSchemeEditor.vue new file mode 100644 index 0000000000..0b64ae6729 --- /dev/null +++ b/src/components/Sing/ColorSchemeEditor.vue @@ -0,0 +1,498 @@ + + + + + diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 5a8a3c6514..0b49b05374 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -1437,16 +1437,14 @@ const contextMenuData = computed(() => { .score-sequencer { backface-visibility: hidden; display: grid; - grid-template-rows: 30px 1fr; + grid-template-rows: 40px 1fr; grid-template-columns: 48px 1fr; } .sequencer-corner { grid-row: 1; grid-column: 1; - background: colors.$background; - border-top: 1px solid colors.$sequencer-sub-divider; - border-bottom: 1px solid colors.$sequencer-sub-divider; + background: var(--md-sys-color-surface-container-high); } .sequencer-ruler { @@ -1471,12 +1469,51 @@ const contextMenuData = computed(() => { } } +.sequencer-grid { + display: block; + pointer-events: none; +} + +.sequencer-grid-cell { + display: block; + stroke: var(--md-sys-color-surface-variant); + stroke-width: 1; +} + +.sequencer-grid-octave-cell { + stroke: var(--md-sys-color-outline); +} + +.sequencer-grid-octave-line { + backface-visibility: hidden; + stroke: var(--md-sys-color-outline); +} + +.sequencer-grid-cell-white { + fill: var(--md-sys-color-background); +} + +.sequencer-grid-cell-black { + fill: var(--md-sys-color-surface-variant); +} + +.sequencer-grid-measure-line { + backface-visibility: hidden; + stroke: var(--md-sys-color-outline); +} + +.sequencer-grid-beat-line { + backface-visibility: hidden; + stroke: var(--md-sys-color-outline); + opacity: 0.6; +} + .sequencer-guideline { position: absolute; top: 0; left: -1px; width: 2px; - background: hsl(130, 35%, 82%); + background: var(--md-sys-color-secondary-container); pointer-events: none; } @@ -1507,15 +1544,15 @@ const contextMenuData = computed(() => { left: -1px; width: 2px; height: 100%; - background: rgba(colors.$display-rgb, 0.6); + background: var(--md-sys-color-inverse-surface); will-change: transform; } .rect-select-preview { pointer-events: none; position: absolute; - border: 2px solid rgba(colors.$primary-rgb, 0.5); - background: rgba(colors.$primary-rgb, 0.25); + border: 1px dashed var(--md-sys-color-secondary); + background: var(--md-sys-color-secondary-container); } .cursor-draw { @@ -1529,6 +1566,24 @@ const contextMenuData = computed(() => { bottom: 16px; right: 32px; width: 80px; + + :deep(.q-slider__track) { + background: var(--md-sys-color-surface-variant); + color: var(--md-sys-color-on-surface-variant); + } + + :deep(.q-slider__thumb) { + color: var(--md-sys-color-on-surface-variant); + } + + &:hover { + :deep(.q-slider__track) { + color: var(--md-sys-color-on-surface-variant); + } + :deep(.q-slider__thumb) { + color: var(--md-sys-color-on-surface-variant); + } + } } .zoom-y-slider { @@ -1536,5 +1591,23 @@ const contextMenuData = computed(() => { bottom: 40px; right: 16px; height: 80px; + + :deep(.q-slider__track) { + background: var(--md-sys-color-surface-variant); + color: var(--md-sys-color-on-surface-variant); + } + + :deep(.q-slider__thumb) { + color: var(--md-sys-color-on-surface-variant); + } + + &:hover { + :deep(.q-slider__track) { + color: var(--md-sys-color-on-surface-variant); + } + :deep(.q-slider__thumb) { + color: var(--md-sys-color-on-surface-variant); + } + } } diff --git a/src/components/Sing/SequencerGrid.vue b/src/components/Sing/SequencerGrid.vue index bbfafd1b1e..f1e13bc269 100644 --- a/src/components/Sing/SequencerGrid.vue +++ b/src/components/Sing/SequencerGrid.vue @@ -1,70 +1,85 @@ @@ -125,34 +140,43 @@ const gridHeight = computed(() => { .sequencer-grid-cell { display: block; - stroke: rgba(colors.$sequencer-sub-divider-rgb, 0.3); - stroke-width: 1; + stroke: 0; } -.sequencer-grid-octave-cell { - stroke: colors.$sequencer-main-divider; +.sequencer-grid-cell-white { + fill: var(--md-custom-color-sing-grid-cell-white); } -.sequencer-grid-octave-line { - backface-visibility: hidden; - stroke: colors.$sequencer-main-divider; +.sequencer-grid-cell-black { + fill: var(--md-custom-color-sing-grid-cell-black); } -.sequencer-grid-cell-white { - fill: colors.$sequencer-whitekey-cell; +.sequencer-grid-vertical-line { + stroke: var(--md-custom-color-sing-grid-vertical-line); + stroke-width: 1px; } -.sequencer-grid-cell-black { - fill: colors.$sequencer-blackkey-cell; +.sequencer-grid-horizontal-line { + backface-visibility: hidden; + stroke: var(--md-custom-color-sing-grid-horizontal-line); + stroke-width: 1px; +} + +.sequencer-grid-octave-line { + backface-visibility: hidden; + stroke: var(--md-custom-color-sing-grid-octave-line); + stroke-width: 1px; } .sequencer-grid-measure-line { backface-visibility: hidden; - stroke: colors.$sequencer-main-divider; + stroke: var(--md-custom-color-sing-grid-measure-line); + stroke-width: 2px; } .sequencer-grid-beat-line { backface-visibility: hidden; - stroke: colors.$sequencer-sub-divider; + stroke: var(--md-custom-color-sing-grid-beat-line); + stroke-width: 1px; } diff --git a/src/components/Sing/SequencerKeys.vue b/src/components/Sing/SequencerKeys.vue index cd79b32764..1d23fdd152 100644 --- a/src/components/Sing/SequencerKeys.vue +++ b/src/components/Sing/SequencerKeys.vue @@ -1,6 +1,11 @@ - diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue index ef6edf207a..eef7de2dbf 100644 --- a/src/components/Sing/ToolBar/ToolBar.vue +++ b/src/components/Sing/ToolBar/ToolBar.vue @@ -3,63 +3,79 @@
- - - - - -
+
-
/
+ + + +
@@ -73,14 +89,14 @@ @@ -102,31 +118,32 @@ flat dense round - icon="undo" class="sing-undo-button" :disable="!canUndo" @click="undo" - /> + > + + + > + + store.getters.SELECTED_TRACK.volumeRangeAdjustment, ); +const beatsOptions = computed(() => { + return Array.from({ length: 32 }, (_, i) => ({ + label: (i + 1).toString(), + value: i + 1, + })); +}); + +const beatTypeOptions = computed(() => { + return [2, 4, 8, 16, 32].map((beatType) => ({ + label: beatType.toString(), + value: beatType, + })); +}); + const bpmInputBuffer = ref(120); const beatsInputBuffer = ref(4); const beatTypeInputBuffer = ref(4); @@ -266,20 +297,30 @@ const setBpmInputBuffer = (bpmStr: string | number | null) => { bpmInputBuffer.value = bpmValue; }; -const setBeatsInputBuffer = (beatsStr: string | number | null) => { - const beatsValue = Number(beatsStr); - if (!isValidBeats(beatsValue)) { +const setBeats = (beats: { label: string; value: number }) => { + if (!isValidBeats(beats.value)) { return; } - beatsInputBuffer.value = beatsValue; + store.dispatch("COMMAND_SET_TIME_SIGNATURE", { + timeSignature: { + measureNumber: 1, + beats: beats.value, + beatType: timeSignatures.value[0].beatType, + }, + }); }; -const setBeatTypeInputBuffer = (beatTypeStr: string | number | null) => { - const beatTypeValue = Number(beatTypeStr); - if (!isValidBeatType(beatTypeValue)) { +const setBeatType = (beatType: { label: string; value: number }) => { + if (!isValidBeatType(beatType.value)) { return; } - beatTypeInputBuffer.value = beatTypeValue; + store.dispatch("COMMAND_SET_TIME_SIGNATURE", { + timeSignature: { + measureNumber: 1, + beats: timeSignatures.value[0].beats, + beatType: beatType.value, + }, + }); }; const setKeyRangeAdjustmentInputBuffer = ( @@ -312,18 +353,6 @@ const setTempo = () => { }); }; -const setTimeSignature = () => { - const beats = beatsInputBuffer.value; - const beatType = beatTypeInputBuffer.value; - store.dispatch("COMMAND_SET_TIME_SIGNATURE", { - timeSignature: { - measureNumber: 1, - beats, - beatType, - }, - }); -}; - const setKeyRangeAdjustment = () => { const keyRangeAdjustment = keyRangeAdjustmentInputBuffer.value; store.dispatch("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); @@ -438,25 +467,87 @@ onUnmounted(() => { @use "@/styles/variables" as vars; @use "@/styles/colors" as colors; -.q-input { - :deep(.q-field__control::before) { - border-color: rgba(colors.$display-rgb, 0.3); - } +// フィールドデフォルト +:deep(.q-field__native) { + color: var(--md-sys-color-on-surface); + text-align: center; + font-size: 16px; + font-weight: 500; +} + +/* QInput のアウトラインをoutline-variantにする */ +:deep(.q-input .q-field__control:before, .q-select .q-field__control:before) { + border: 1px solid var(--md-sys-color-outline-variant); +} + +// ラベルのフォントサイズを小さくする() +:deep(.q-input .q-field__label, .q-select .q-field__label) { + font-size: 14px; + color: var(--md-sys-color-on-surface-variant); } -.q-select { - :deep(.q-field__control::before) { - border-color: rgba(colors.$display-rgb, 0.3); +// type:number +:deep(.q-field__native[type="number"]) { + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + cursor: pointer; + } + + // スピンボタンのホバー状態 + &:hover::-webkit-inner-spin-button, + &:hover::-webkit-outer-spin-button { + background: var(--md-sys-color-surface-container-highest); + } + + // スピンボタンのアクティブ状態 + &:active::-webkit-inner-spin-button, + &:active::-webkit-outer-spin-button { + background: var(--md-sys-color-surface-container-low); } } +:deep( + .q-input .q-field__control:hover:before, + .q-select .q-field__control:hover:before + ) { + border: 1px solid var(--md-sys-color-outline); +} + +// オプションメニュー全体の背景色 +:deep(.q-menu) { + background: var(--md-sys-color-surface-container); +} + +// TODO: アクティブ色が効かないので修正したい +:deep(.q-menu .q-item--active) { + //background-color: var(--md-sys-color-secondary-container); + color: var(--md-sys-color-primary); +} + +:deep(.sing-time-signature-field .q-field__control) { + padding: 0; +} + +:deep(.sing-beats .q-field__control) { + background: transparent; + padding: 0 4px; +} + +:deep(.sing-time-signature.beats .q-field__control) { + padding: 0 4px 0 12px; +} + +:deep(.sing-time-signature.beat-type .q-field__control) { + padding: 0 12px 0 4px; +} + .sing-toolbar { - background: colors.$sing-toolbar; + background: var(--md-custom-color-sing-toolbar-container); align-items: center; display: flex; justify-content: space-between; - min-height: 56px; - padding: 0 8px 0 0; + min-height: 64px; + padding: 0 4px 0 4px; width: 100%; } @@ -473,50 +564,137 @@ onUnmounted(() => { flex: 1; } +.sing-adjustment { + height: 56px; + border: 1px solid var(--md-sys-color-outline-variant); + border-left: 0; + border-radius: 0 4px 4px 0; + padding: 0 0 0 8px; + display: flex; + align-items: center; +} + .key-range-adjustment { - margin-left: 16px; - margin-right: 4px; - width: 50px; + margin-right: 0px; + width: 40px; + + :deep(.q-field__control) { + padding: 0 2px; + height: 56px; + + &:before { + border: 1px solid transparent; + } + + &:hover:before { + border-color: transparent; + border-bottom: 1px solid var(--md-sys-color-outline); + } + } } .volume-range-adjustment { - margin-left: 4px; - margin-right: 4px; - width: 50px; + width: 40px; + + :deep(.q-field__control) { + padding: 0 2px; + height: 56px; + + &:before { + border: 1px solid transparent; + } + + &:hover:before { + border-color: transparent; + border-bottom: 1px solid var(--md-sys-color-outline); + } + } } .sing-tempo { - margin-left: 8px; + margin-left: 16px; margin-right: 4px; width: 72px; } .sing-tempo-icon { - color: rgba(colors.$display-rgb, 0.6); padding-right: 0px; position: relative; top: 4px; left: 0; } +.sing-time-signature-field { + height: 56px; + + :deep(.q-field__control) { + height: 56px; + padding: 0 4px; + } + + :deep(.q-field__label) { + font-size: 10px; + top: 7px; + margin-left: 8px; + transform: translateY(0) !important; + color: var(--md-sys-color-on-surface-variant); + opacity: 0.9; + } + + :deep(.q-field__native) { + padding-top: 4px; + } + + :deep(.q-field__control:before) { + border-color: var(--md-sys-color-outline-variant); + } + + :deep(.q-field__control:hover:before) { + border-color: var(--md-sys-color-outline); + } +} + .sing-beats { - align-items: center; display: flex; - margin-left: 8px; - position: relative; -} + align-items: center; + height: 56px; + margin-top: -24px; -.sing-time-signature { - margin: 0; - position: relative; - width: 32px; + &:deep(.q-field__control:before) { + border: 1px solid transparent; + } + + &:deep(.q-field__control:hover:before) { + border-color: transparent; + } } + .sing-beats-separator { - color: rgba(colors.$display-rgb, 0.6); - position: relative; - top: 5px; - margin-right: 8px; + font-weight: 500; + color: var(--md-sys-color-on-surface-variant); pointer-events: none; + transform: translateY(8px); + opacity: 0.56; +} + +.sing-transport-button { + color: var(--md-sys-color-on-surface-variant); +} + +.sing-playback-button { + background: var(--md-sys-color-secondary-container); + color: var(--md-sys-color-on-surface); + &:before { + box-shadow: none; + } + + &.sing-playback-play .q-btn__wrapper .q-icon { + transform: translateX(-0.5px); + } + + &.sing-playback-stop .q-btn__wrapper .q-icon { + transform: translateX(-0.5px); + } } .sing-playhead-position { @@ -525,14 +703,14 @@ onUnmounted(() => { font-size: 28px; font-weight: 700; margin-left: 16px; - color: colors.$display; + color: var(--md-sys-color-on-surface); } .sing-playhead-position-millisec { font-size: 16px; font-weight: 700; margin: 10px 0 0 2px; - color: rgba(colors.$display-rgb, 0.73); + color: var(--md-sys-color-on-surface); } .sing-controls { @@ -542,26 +720,69 @@ onUnmounted(() => { flex: 1; } +.sing-undo-button { + margin-left: 24px; +} + .sing-undo-button, .sing-redo-button { + color: var(--md-sys-color-on-surface-variant); + width: 40px; + height: 40px; &.disabled { - opacity: 0.4 !important; + opacity: 0.38 !important; } } .sing-redo-button { - margin-right: 16px; + margin-right: 8px; } .sing-volume-icon { margin-right: 8px; opacity: 0.6; + + :deep { + color: var(--md-sys-color-on-surface-variant); + } } + .sing-volume { margin-right: 16px; width: 72px; + + :deep(.q-slider__track) { + background: var(--md-sys-color-surface-variant); + color: var(--md-sys-color-on-surface-variant); + } + + :deep(.q-slider__thumb) { + color: var(--md-sys-color-on-surface-variant); + } + + &:hover { + :deep(.q-slider__track) { + color: var(--md-sys-color-on-surface-variant); + } + :deep(.q-slider__thumb) { + color: var(--md-sys-color-on-surface-variant); + } + } } .sing-snap { - min-width: 104px; + min-width: 80px; + + &:deep(.q-field__control:before) { + border: 1px solid var(--md-sys-color-outline-variant); + } + + :deep(.q-field__control:hover:before) { + border-color: var(--md-sys-color-outline); + } + + :deep(.q-field__label) { + font-size: 14px; + color: var(--md-sys-color-on-surface-variant); + } } diff --git a/src/composables/useColorScheme.ts b/src/composables/useColorScheme.ts new file mode 100644 index 0000000000..e15b8a26da --- /dev/null +++ b/src/composables/useColorScheme.ts @@ -0,0 +1,85 @@ +import { computed, watch } from "vue"; +import { useStore } from "@/store"; +import { ColorSchemeConfig, ColorScheme } from "@/type/preload"; +import { colorSchemeToCssVariables, arrayFromRgba } from "@/helpers/colors"; + +export function useColorScheme() { + const store = useStore(); + + const colorSchemeConfig = computed( + () => + store.state.colorSchemeSetting.currentColorScheme?.config ?? undefined, + ); + + const availableColorSchemeConfigs = computed( + () => store.state.colorSchemeSetting.availableColorSchemeConfigs, + ); + + const currentColorScheme = computed( + () => store.state.colorSchemeSetting.currentColorScheme, + ); + + const isDarkMode = computed({ + get: () => colorSchemeConfig.value?.isDark ?? false, + set: (value: boolean) => updateColorScheme({ isDark: value }), + }); + + const updateColorScheme = async ( + partialConfig: Partial, + ) => { + if (!colorSchemeConfig.value) return; + const newConfig = { ...colorSchemeConfig.value, ...partialConfig }; + await store.dispatch("SET_COLOR_SCHEME", { colorSchemeConfig: newConfig }); + }; + + const selectColorScheme = async (selectedSchemeName: string) => { + const selected = availableColorSchemeConfigs.value.find( + (scheme) => scheme.name === selectedSchemeName, + ); + if (selected) { + await updateColorScheme(selected); + } + }; + + const systemColorsRgba = computed(() => { + if (!currentColorScheme.value) return {}; + // RGBAに変換 + return Object.entries(currentColorScheme.value.systemColors).reduce( + (acc, [key, value]) => { + acc[key] = arrayFromRgba(value); + return acc; + }, + {} as Record, + ); + }); + + const resetColorScheme = () => store.dispatch("INITIALIZE_COLOR_SCHEME"); + + const applyColorScheme = () => { + if (!currentColorScheme.value) return; + const cssVariables = colorSchemeToCssVariables(currentColorScheme.value); + Object.entries(cssVariables).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); + }; + + watch( + currentColorScheme, + () => { + applyColorScheme(); + }, + { deep: true, immediate: true }, + ); + + return { + colorSchemeConfig, + systemColorsRgba, + availableColorSchemeConfigs, + currentColorScheme, + isDarkMode, + updateColorScheme, + selectColorScheme, + resetColorScheme, + applyColorScheme, + }; +} diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts new file mode 100644 index 0000000000..67aefe6069 --- /dev/null +++ b/src/helpers/colors.ts @@ -0,0 +1,498 @@ +import { + hexFromArgb, + argbFromHex, + Hct, + Contrast, + DynamicScheme, + SchemeContent, + SchemeTonalSpot, + SchemeNeutral, + SchemeVibrant, + SchemeExpressive, + SchemeFidelity, + SchemeMonochrome, + SchemeRainbow, + SchemeFruitSalad, + TonalPalette, + MaterialDynamicColors, +} from "@material/material-color-utilities"; + +import { + ColorScheme, + ColorSchemeConfig, + ColorSchemeCorePalettes, + ColorSchemeAdjustment, + CustomPaletteColor, +} from "@/type/preload"; + +const SCHEME_CONSTRUCTORS = { + content: SchemeContent, + tonalSpot: SchemeTonalSpot, + neutral: SchemeNeutral, + vibrant: SchemeVibrant, + expressive: SchemeExpressive, + fidelity: SchemeFidelity, + monochrome: SchemeMonochrome, + rainbow: SchemeRainbow, + fruitSalad: SchemeFruitSalad, +} as const; + +const PALETTE_KEYS = [ + "primary", + "secondary", + "tertiary", + "neutral", + "neutralVariant", + "error", +] as const; + +const TONES = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100] as const; + +export const rgbaFromArgb = (argb: number, alpha: number = 1): string => { + const r = (argb >> 16) & 255; + const g = (argb >> 8) & 255; + const b = argb & 255; + return `rgba(${r}, ${g}, ${b}, ${alpha.toFixed(2)})`; +}; + +export const arrayFromRgba = (rgba: string): number[] => { + const match = rgba.match(/^rgba\(([0-9]+), ([0-9]+), ([0-9]+), ([0-9.]+)\)$/); + return match ? match.slice(1, 5).map(Number) : []; +}; + +const createAdjustedPalette = ( + palette: TonalPalette, + adjustment: ColorSchemeAdjustment, +): TonalPalette => { + const baseHct = adjustment.hex + ? Hct.fromInt(argbFromHex(adjustment.hex)) + : Hct.from(palette.hue, palette.chroma, 50); + + const adjustedHct = Hct.from( + adjustment.hue ?? baseHct.hue, + adjustment.chroma ?? baseHct.chroma, + adjustment.tone ?? baseHct.tone, + ); + + return TonalPalette.fromHueAndChroma(adjustedHct.hue, adjustedHct.chroma); +}; + +const generateDynamicScheme = (config: ColorSchemeConfig): DynamicScheme => { + const { + sourceColor, + variant = "tonalSpot", + isDark = false, + contrastLevel = 0, + adjustments, + } = config; + const sourceHct = Hct.fromInt(argbFromHex(sourceColor)); + const SchemeConstructor = SCHEME_CONSTRUCTORS[variant]; + let scheme = new SchemeConstructor(sourceHct, isDark, contrastLevel); + + if (adjustments) { + const adjustedPalettes = Object.entries(adjustments).reduce( + (acc, [key, adjustment]) => { + if (adjustment) { + acc[`${key}Palette`] = createAdjustedPalette( + scheme[`${key}Palette` as keyof DynamicScheme] as TonalPalette, + adjustment, + ); + } + return acc; + }, + {} as Partial>, + ); + + scheme = Object.create(Object.getPrototypeOf(scheme), { + ...Object.getOwnPropertyDescriptors(scheme), + ...Object.fromEntries( + Object.entries(adjustedPalettes).map(([key, value]) => [ + key, + { value, writable: false }, + ]), + ), + }); + } + + return scheme; +}; + +const generateSystemColors = ( + scheme: DynamicScheme, +): Record => { + return Object.entries(MaterialDynamicColors).reduce( + (acc, [name, color]) => { + if (typeof color === "object" && "getArgb" in color) { + acc[name] = hexFromArgb(color.getArgb(scheme)); + } + return acc; + }, + {} as Record, + ); +}; + +const generatePaletteTones = ( + scheme: DynamicScheme, +): Record> => { + return PALETTE_KEYS.reduce( + (acc, key) => { + acc[key] = Object.fromEntries( + TONES.map((tone) => [ + tone, + hexFromArgb( + ( + scheme[`${key}Palette` as keyof DynamicScheme] as TonalPalette + ).tone(tone), + ), + ]), + ); + return acc; + }, + {} as Record>, + ); +}; + +const adjustCustomPaletteColors = ( + customColors: CustomPaletteColor[], + scheme: DynamicScheme, + isDark: boolean, +): Record => { + const sortedColors = [...customColors].sort((a, b) => { + const toneA = isDark ? a.darkTone : a.lightTone; + const toneB = isDark ? b.darkTone : b.lightTone; + return toneA - toneB; + }); + + const adjustTone = ( + tone: number, + index: number, + contrastLevel: number, + ): number => { + const direction = index < sortedColors.length / 2 ? -1 : 1; + const adjustmentFactor = + Math.abs(index - (sortedColors.length - 1) / 2) / + ((sortedColors.length - 1) / 2); + const adjustment = direction * contrastLevel * 10 * adjustmentFactor; + return Math.max(0, Math.min(100, tone + adjustment)); + }; + + return sortedColors.reduce( + (acc, color, index) => { + const palette = scheme[ + `${color.palette}Palette` as keyof DynamicScheme + ] as TonalPalette; + const tone = isDark ? color.darkTone : color.lightTone; + const adjustedTone = adjustTone(tone, index, scheme.contrastLevel); + acc[color.name] = hexFromArgb(palette.tone(adjustedTone)); + return acc; + }, + {} as Record, + ); +}; + +export const generateColorScheme = (config: ColorSchemeConfig): ColorScheme => { + const scheme = generateDynamicScheme(config); + const systemColors = generateSystemColors(scheme); + const paletteTones = generatePaletteTones(scheme); + const customPaletteColors = adjustCustomPaletteColors( + config.customPaletteColors, + scheme, + config.isDark, + ); + + const customDefinedColors = config.customDefinedColors.reduce( + (acc, color) => { + acc[color.name] = color.value; + return acc; + }, + {} as Record, + ); + + const colorScheme = { + scheme, + systemColors, + paletteTones, + customPaletteColors, + customDefinedColors, + config, + }; + + const results = evaluateContrastAndGenerateResults(colorScheme, config); + printContrastResults(results); + + return colorScheme; +}; + +export const colorSchemeToCssVariables = ( + colorScheme: ColorScheme, +): Record => { + const cssVars: Record = {}; + + const toKebabCase = (str: string) => { + return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + }; + + const setColorVar = (prefix: string, name: string, color: string) => { + const rgba = arrayFromRgba(color); + if (rgba.length >= 3) { + cssVars[`${prefix}${name}-rgb`] = `${rgba[0]}, ${rgba[1]}, ${rgba[2]}`; + } + cssVars[`${prefix}${name}`] = color; + }; + + Object.entries(colorScheme.systemColors).forEach(([name, color]) => { + const cssName = toKebabCase(name); + setColorVar("--md-sys-color-", cssName, color); + }); + + Object.entries(colorScheme.paletteTones).forEach(([key, tones]) => { + Object.entries(tones).forEach(([tone, color]) => { + const cssName = toKebabCase(key); + setColorVar(`--md-ref-palette-${cssName}-`, tone, color); + }); + }); + + Object.entries(colorScheme.customPaletteColors).forEach(([name, color]) => { + const cssName = toKebabCase(name); + setColorVar("--md-custom-color-", cssName, color); + }); + + Object.entries(colorScheme.customDefinedColors).forEach(([name, color]) => { + const cssName = toKebabCase(name); + setColorVar("--md-custom-color-", cssName, color); + }); + + return cssVars; +}; + +interface ContrastCheckResult { + color1: string; + color2: string; + contrastRatio: number; + checkType: string; + wcagAANormal: boolean; + wcagAALarge: boolean; + wcagAAANormal: boolean; + wcagAAALarge: boolean; + functionalCheck: string; + designCheck: string; + expectedContrastRatio?: number; +} + +function getContrastRatio( + color1: string, + color2: string, + colors: Record, +): number { + const hct1 = Hct.fromInt(argbFromHex(colors[color1])); + const hct2 = Hct.fromInt(argbFromHex(colors[color2])); + return Contrast.ratioOfTones(hct1.tone, hct2.tone); +} + +function evaluateContrast( + contrastRatio: number, + checkType: string, + color1: string, + color2: string, + expectedContrastRatio?: number, +): ContrastCheckResult { + const wcagAANormal = contrastRatio >= 4.5; + const wcagAALarge = contrastRatio >= 3; + const wcagAAANormal = contrastRatio >= 7; + const wcagAAALarge = contrastRatio >= 4.5; + + // 機能チェック + let functionalCheck = ""; + // デザイン基準チェック + let designCheck = ""; + + switch (checkType) { + case "text": + functionalCheck = wcagAAANormal + ? "Pass AAA" + : wcagAANormal + ? "Pass AA" + : "Fail"; + designCheck = "N/A"; + break; + case "largeText": + functionalCheck = wcagAAALarge + ? "Pass AAA" + : wcagAALarge + ? "Pass AA" + : "Fail"; + designCheck = "N/A"; + break; + case "ui": + functionalCheck = wcagAALarge ? "Pass" : "Fail"; + designCheck = contrastRatio >= 3.0 ? "Good" : "Poor"; + break; + case "structure": + functionalCheck = contrastRatio >= 1.5 ? "Pass" : "Fail"; + designCheck = contrastRatio >= 1.5 ? "Good" : "Poor"; + break; + case "decorative": + functionalCheck = "N/A"; + designCheck = contrastRatio >= 1.2 ? "Good" : "Poor"; + break; + case "custom": + functionalCheck = "Custom"; + designCheck = "Custom"; + break; + } + + return { + color1, + color2, + contrastRatio, + checkType, + wcagAANormal, + wcagAALarge, + wcagAAANormal, + wcagAAALarge, + functionalCheck, + designCheck, + expectedContrastRatio, + }; +} + +export function evaluateContrastAndGenerateResults( + colorScheme: ColorScheme, + config: ColorSchemeConfig, +): ContrastCheckResult[] { + const results: ContrastCheckResult[] = []; + const colors: Record = { + ...colorScheme.systemColors, + ...colorScheme.customPaletteColors, + }; + + function checkContrast( + color1: string, + color2: string, + checkType: string, + expectedContrastRatio?: number, + ) { + if (!colors[color1] || !colors[color2]) { + return; + } + + const contrastRatio = getContrastRatio(color1, color2, colors); + const result = evaluateContrast( + contrastRatio, + checkType, + color1, + color2, + expectedContrastRatio, + ); + if ( + expectedContrastRatio == undefined || + contrastRatio < expectedContrastRatio + ) { + results.push(result); + } + } + + const colorPairs = [ + { color1: "primary", color2: "onPrimary", type: "text", expected: 4.5 }, + { + color1: "primaryContainer", + color2: "onPrimaryContainer", + type: "text", + expected: 4.5, + }, + { color1: "secondary", color2: "onSecondary", type: "text", expected: 4.5 }, + { + color1: "secondaryContainer", + color2: "onSecondaryContainer", + type: "text", + expected: 4.5, + }, + { color1: "tertiary", color2: "onTertiary", type: "text", expected: 4.5 }, + { + color1: "tertiaryContainer", + color2: "onTertiaryContainer", + type: "text", + expected: 4.5, + }, + { color1: "error", color2: "onError", type: "text", expected: 4.5 }, + { + color1: "errorContainer", + color2: "onErrorContainer", + type: "text", + expected: 4.5, + }, + { + color1: "background", + color2: "onBackground", + type: "text", + expected: 4.5, + }, + { color1: "surface", color2: "onSurface", type: "text", expected: 4.5 }, + { + color1: "surfaceVariant", + color2: "onSurfaceVariant", + type: "text", + expected: 4.5, + }, + { + color1: "inverseSurface", + color2: "inverseOnSurface", + type: "text", + expected: 4.5, + }, + { color1: "outline", color2: "background", type: "ui", expected: 3 }, + { color1: "outlineVariant", color2: "surface", type: "ui", expected: 1.5 }, + ]; + + colorPairs.forEach((pair) => { + checkContrast(pair.color1, pair.color2, pair.type, pair.expected); + }); + + ["primary", "secondary", "tertiary", "error"].forEach((color) => { + checkContrast(color, "background", "ui", 3); + checkContrast(color, "surface", "ui", 3); + }); + + config.customPaletteColors.forEach((customColor: CustomPaletteColor) => { + if (customColor.contrastVs) { + Object.entries(customColor.contrastVs).forEach( + ([contrastColor, expectedRatio]) => { + checkContrast( + customColor.name, + contrastColor, + "custom", + expectedRatio, + ); + }, + ); + } else { + checkContrast(customColor.name, "background", "ui"); + checkContrast(customColor.name, "surface", "ui"); + } + }); + + return results; +} + +export function printContrastResults(results: ContrastCheckResult[]): void { + if (results.length === 0) { + console.log("All color check passed."); + return; + } + + console.warn("check following color contrast:"); + results.forEach((result) => { + console.warn(` + Colors: ${result.color1} vs ${result.color2} + Check Type: ${result.checkType} + Contrast Ratio: ${result.contrastRatio.toFixed(2)}:1 + ${result.expectedContrastRatio ? `Expect Contrast Ratio: ${result.expectedContrastRatio.toFixed(2)}:1` : ""} + WCAG AA (Normal Text): ${result.wcagAANormal ? "Pass" : "Fail"} + WCAG AA (Large Text/UI): ${result.wcagAALarge ? "Pass" : "Fail"} + WCAG AAA (Normal Text): ${result.wcagAAANormal ? "Pass" : "Fail"} + WCAG AAA (Large Text): ${result.wcagAAALarge ? "Pass" : "Fail"} + Functional Check: ${result.functionalCheck} + Design Check: ${result.designCheck} + `); + }); +} diff --git a/src/store/setting.ts b/src/store/setting.ts index ebd2dcaf17..28a740f9bb 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -2,6 +2,10 @@ import { Dark, setCssVar, colors } from "quasar"; import { SettingStoreState, SettingStoreTypes } from "./type"; import { createDotNotationUILockAction as createUILockAction } from "./ui"; import { createDotNotationPartialStore as createPartialStore } from "./vuex"; +import { + generateColorScheme, + colorSchemeToCssVariables, +} from "@/helpers/colors"; import { HotkeySettingType, SavingSetting, @@ -12,9 +16,14 @@ import { EngineId, ConfirmedTips, RootMiscSettingType, + ColorScheme, + ColorSchemeConfig, } from "@/type/preload"; +import { createLogger } from "@/domain/frontend/log"; import { IsEqual } from "@/type/utility"; +const logger = createLogger("store/setting"); + export const settingStoreState: SettingStoreState = { savingSetting: { fileEncoding: "UTF-8", @@ -36,6 +45,10 @@ export const settingStoreState: SettingStoreState = { currentTheme: "Default", availableThemes: [], }, + colorSchemeSetting: { + currentColorScheme: undefined as unknown as ColorScheme, + availableColorSchemeConfigs: [] as ColorSchemeConfig[], + }, editorFont: "default", showTextLineNumber: false, showAddAudioItemButton: true, @@ -49,6 +62,7 @@ export const settingStoreState: SettingStoreState = { enableMultiSelect: false, shouldKeepTuningOnTextChange: false, enablePitchEditInSongEditor: false, + enableColorSchemeEditor: false, }, splitTextWhenPaste: "PERIOD_AND_NEW_LINE", splitterPosition: { @@ -89,6 +103,9 @@ export const settingStore = createPartialStore({ }); } + // TODO: Hydrate + actions.INITIALIZE_COLOR_SCHEME(); + actions.SET_ACCEPT_RETRIEVE_TELEMETRY({ acceptRetrieveTelemetry: await window.backend.getSetting( "acceptRetrieveTelemetry", @@ -281,6 +298,63 @@ export const settingStore = createPartialStore({ }, }, + INITIALIZE_COLOR_SCHEME: { + mutation(state, { currentColorScheme, availableColorSchemeConfigs }) { + state.colorSchemeSetting = { + currentColorScheme, + availableColorSchemeConfigs, + }; + }, + action: createUILockAction(async ({ commit, state }) => { + try { + const availableColorSchemeConfigs = + await window.backend.getColorSchemeConfigs(); + // デフォルト + const defaultSchemeConfigWorkaround = availableColorSchemeConfigs[0]; + const isDark = state.themeSetting.currentTheme === "Dark"; + const currentColorScheme = generateColorScheme({ + ...defaultSchemeConfigWorkaround, + isDark, + }); + commit("INITIALIZE_COLOR_SCHEME", { + currentColorScheme, + availableColorSchemeConfigs, + }); + + logger.info("initialized color scheme"); + + const cssVariables = colorSchemeToCssVariables(currentColorScheme); + Object.entries(cssVariables).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); + } catch (error) { + logger.error(`Error initializing color scheme: ${error}`); + throw error; + } + }), + }, + SET_COLOR_SCHEME: { + mutation(state, { colorScheme }) { + state.colorSchemeSetting.currentColorScheme = colorScheme; + }, + action: createUILockAction( + async ({ commit, state }, { colorSchemeConfig }) => { + try { + const isDark = + colorSchemeConfig.isDark ?? + state.themeSetting.currentTheme === "Dark"; + const updatedConfig = { ...colorSchemeConfig, isDark }; + const colorScheme = generateColorScheme(updatedConfig); + commit("SET_COLOR_SCHEME", { colorScheme }); + logger.info("set color scheme"); + } catch (error) { + logger.error(`Error setting color scheme: ${error}`); + throw error; + } + }, + ), + }, + SET_ACCEPT_RETRIEVE_TELEMETRY: { mutation(state, { acceptRetrieveTelemetry }) { state.acceptRetrieveTelemetry = acceptRetrieveTelemetry; diff --git a/src/store/type.ts b/src/store/type.ts index b94864d7aa..5f6ce68941 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -30,6 +30,7 @@ import { SavingSetting, ThemeConf, ThemeSetting, + ColorSchemeSetting, ExperimentalSettingType, ToolbarSettingType, UpdateInfo, @@ -52,6 +53,8 @@ import { RootMiscSettingType, EditorType, NoteId, + ColorSchemeConfig, + ColorScheme, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { @@ -1528,6 +1531,7 @@ export type SettingStoreState = { engineInfos: Record; engineManifests: Record; themeSetting: ThemeSetting; + colorSchemeSetting: ColorSchemeSetting; acceptTerms: AcceptTermsStatus; acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus; experimentalSetting: ExperimentalSettingType; @@ -1573,6 +1577,21 @@ export type SettingStoreTypes = { action(payload: { currentTheme: string }): void; }; + INITIALIZE_COLOR_SCHEME: { + mutation: { + currentColorScheme: ColorScheme; + availableColorSchemeConfigs: ColorSchemeConfig[]; + }; + action(): void; + }; + + SET_COLOR_SCHEME: { + mutation: { + colorScheme: ColorScheme; + }; + action(payload: { colorSchemeConfig: ColorSchemeConfig }): void; + }; + SET_ACCEPT_RETRIEVE_TELEMETRY: { mutation: { acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus }; action(payload: { diff --git a/src/styles/colors.scss b/src/styles/colors.scss index 644658f7a5..c301c7b185 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -28,15 +28,6 @@ $active-point-focus-rgb: var(--color-active-point-focus-rgb); $active-point-hover: var(--color-active-point-hover); $active-point-hover-rgb: var(--color-active-point-hover-rgb); -$sequencer-whitekey-cell: var(--color-sequencer-whitekey-cell); -$sequencer-blackkey-cell: var(--color-sequencer-blackkey-cell); -$sequencer-main-divider: var(--color-sequencer-main-divider); -$sequencer-main-divider-rgb: var(--color-sequencer-main-divider-rgb); -$sequencer-sub-divider: var(--color-sequencer-sub-divider); -$sequencer-sub-divider-rgb: var(--color-sequencer-sub-divider-rgb); -$sequencer-white-key: var(--color-sequencer-white-key); -$sequencer-black-key: var(--color-sequencer-black-key); - // ダークテーマと通常テーマで変わる色 :root { --color-toolbar: var(--color-primary); @@ -64,7 +55,6 @@ $sequencer-black-key: var(--color-sequencer-black-key); :root[is-dark-theme="true"] { --color-toolbar: var(--color-surface); --color-toolbar-rgb: var(--color-surface-rgb); - --color-sing-toolbar: var(--color-surface); --color-toolbar-button: var(--color-primary); --color-toolbar-button-rgb: var(--color-primary-rgb); @@ -87,8 +77,6 @@ $sequencer-black-key: var(--color-sequencer-black-key); $toolbar: var(--color-toolbar); $toolbar-rgb: var(--color-toolbar-rgb); -$sing-toolbar: var(--color-sing-toolbar); - $toolbar-button: var(--color-toolbar-button); $toolbar-button-rgb: var(--color-toolbar-button-rgb); diff --git a/src/styles/fonts.scss b/src/styles/fonts.scss index 2911455b92..b7d7467fcd 100644 --- a/src/styles/fonts.scss +++ b/src/styles/fonts.scss @@ -2,12 +2,22 @@ font-family: "Unhinted Rounded M+ 1p"; src: url("../fonts/unhinted-rounded-mplus-1p-regular.woff2"); - font-weight: normal; + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: "Unhinted Rounded M+ 1p"; + src: url("../fonts/unhinted-rounded-mplus-1p-medium.woff2"); + + font-weight: 500; + font-style: normal; } @font-face { font-family: "Unhinted Rounded M+ 1p"; src: url("../fonts/unhinted-rounded-mplus-1p-bold.woff2"); - font-weight: bold; + font-weight: 700; + font-style: normal; } diff --git a/src/styles/tokens.scss b/src/styles/tokens.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/type/ipc.ts b/src/type/ipc.ts index 599e4a717d..c86f124525 100644 --- a/src/type/ipc.ts +++ b/src/type/ipc.ts @@ -11,6 +11,7 @@ import { EngineSettingType, EngineId, MessageBoxReturnValue, + ColorSchemeConfig, } from "@/type/preload"; import { AltPortInfos } from "@/store/type"; import { Result } from "@/type/result"; @@ -249,6 +250,11 @@ export type IpcIHData = { return: ThemeSetting | void; }; + GET_COLOR_SCHEME_CONFIGS: { + args: []; + return: ColorSchemeConfig[]; + }; + ON_VUEX_READY: { args: []; return: void; diff --git a/src/type/preload.ts b/src/type/preload.ts index 01a3bdecb7..24357d2225 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { DynamicScheme } from "@material/material-color-utilities"; import { IpcSOData } from "./ipc"; import { AltPortInfos } from "@/store/type"; import { Result } from "@/type/result"; @@ -291,6 +292,7 @@ export interface Sandbox { uninstallVvppEngine(engineId: EngineId): Promise; validateEngineDir(engineDir: string): Promise; reloadApp(obj: { isMultiEngineOffMode?: boolean }): Promise; + getColorSchemeConfigs(): Promise; } export type AppInfos = { @@ -557,6 +559,79 @@ export type ThemeSetting = { availableThemes: ThemeConf[]; }; +export type ColorSchemeSetting = { + currentColorScheme: ColorScheme; + availableColorSchemeConfigs: ColorSchemeConfig[]; +}; + +// カラースキーマの種類(M3準拠) +export type ColorSchemeVariant = + | "content" + | "tonalSpot" + | "neutral" + | "vibrant" + | "expressive" + | "fidelity" + | "monochrome" + | "rainbow" + | "fruitSalad"; + +// カラーパレットのキー(M3準拠) +export type ColorSchemeCorePalettes = + | "primary" + | "secondary" + | "tertiary" + | "neutral" + | "neutralVariant" + | "error"; + +// カラー事前調整 +export interface ColorSchemeAdjustment { + hue?: number; + chroma?: number; + tone?: number; + hex?: string; +} + +// パレットから取得するカスタムカラー +export interface CustomPaletteColor { + name: string; + palette: ColorSchemeCorePalettes; + lightTone: number; + darkTone: number; + blend: boolean; + contrastVs: Record; +} + +// 定義済みカスタムカラー +export interface CustomDefinedColor { + name: string; + value: string; + blend: boolean; +} + +// カラースキーマのベース設定(ここから生成) +export interface ColorSchemeConfig { + name: string; + sourceColor: string; + variant: ColorSchemeVariant; + isDark: boolean; + contrastLevel: number; + adjustments: Partial>; + customPaletteColors: CustomPaletteColor[]; + customDefinedColors: CustomDefinedColor[]; +} + +// カラースキーマ +export interface ColorScheme { + scheme: DynamicScheme; + systemColors: Record; + paletteTones: Record>; + customPaletteColors: Record; + customDefinedColors: Record; + config: ColorSchemeConfig; +} + export const experimentalSettingSchema = z.object({ enablePreset: z.boolean().default(false), shouldApplyDefaultPresetOnVoiceChanged: z.boolean().default(false), @@ -565,6 +640,7 @@ export const experimentalSettingSchema = z.object({ enableMultiSelect: z.boolean().default(false), shouldKeepTuningOnTextChange: z.boolean().default(false), enablePitchEditInSongEditor: z.boolean().default(false), + enableColorSchemeEditor: z.boolean().default(false), }); export type ExperimentalSettingType = z.infer; diff --git "a/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts" "b/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts" index 43ed5ee1d9..48aa8a0591 100644 --- "a/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts" +++ "b/tests/e2e/browser/song/\343\202\275\343\203\263\343\202\260.spec.ts" @@ -72,15 +72,33 @@ test("ダブルクリックで歌詞を編集できる", async ({ page }) => { const getCurrentNoteLyric = async (note: Locator) => await note.getByTestId("note-lyric").textContent(); + // ノートを追加し、表示されるまで待つ await sequencer.click({ position: { x: 107, y: 171 } }); + await page.waitForSelector(".note"); - const note = sequencer.locator(".note"); + // ノートの歌詞を取得 + const note = sequencer.locator(".note").first(); const beforeLyric = await getCurrentNoteLyric(note); - await sequencer.click({ position: { x: 107, y: 171 }, clickCount: 2 }); // ダブルクリック + // ノートをダブルクリックし、入力フィールドが表示されるまで待つ + await note.dblclick(); + await page.waitForSelector(".lyric-input"); - await sequencer.locator(".lyric-input").fill("あ"); - await page.keyboard.press("Enter"); + // 歌詞を入力し、Enterキーを押す + const lyricInput = sequencer.locator(".lyric-input"); + await lyricInput.fill("あ"); + await lyricInput.press("Enter"); + + // 変更が反映されるまで待つ + await page.waitForFunction((beforeLyric) => { + const lyricElement = document.querySelector( + '.note [data-testid="note-lyric"]', + ); + return lyricElement && lyricElement.textContent !== beforeLyric; + }, beforeLyric); + + // 歌詞が変更されたことを確認 const afterLyric = await getCurrentNoteLyric(note); expect(afterLyric).not.toEqual(beforeLyric); + expect(afterLyric).toEqual("あ"); }); diff --git "a/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" "b/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" index 8c4fd69899..0ff1f22e01 100644 Binary files "a/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" and "b/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" differ diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" index eb3c25ab6a..e3f99b4426 100644 Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-0-browser-win32.png" differ diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" index 571f3498bb..44535d8185 100644 Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-1-browser-win32.png" differ diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" index 2f821183c8..ede641a60e 100644 Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-2-browser-win32.png" differ diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" index 9d8a1807ac..8fc6ce8b3f 100644 Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-3-browser-win32.png" differ diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" index 9d8a1807ac..8fc6ce8b3f 100644 Binary files "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" and "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210-4-browser-win32.png" differ