From ff545db61794a3baf21d126dc22425ef40678e0c Mon Sep 17 00:00:00 2001 From: Romot Date: Sun, 16 Jun 2024 19:18:37 +0900 Subject: [PATCH 01/37] =?UTF-8?q?config=E3=81=8B=E3=82=89=E3=82=AB?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 11 ++++ package.json | 1 + src/helpers/colors.ts | 149 ++++++++++++++++++++++++++++++++++++++++++ src/main.ts | 59 +++++++++++++++++ src/styles/fonts.scss | 7 ++ 5 files changed, 227 insertions(+) create mode 100644 src/helpers/colors.ts diff --git a/package-lock.json b/package-lock.json index e026e14817..b5b94bca58 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.2.7", "@quasar/extras": "1.10.10", "@tonejs/midi": "2.0.28", "async-lock": "1.4.0", @@ -1323,6 +1324,11 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/@material/material-color-utilities": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz", + "integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==" + }, "node_modules/@nestjs/axios": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.0.tgz", @@ -14499,6 +14505,11 @@ } } }, + "@material/material-color-utilities": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz", + "integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==" + }, "@nestjs/axios": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.0.tgz", diff --git a/package.json b/package.json index c0dfe0e986..df65e57b23 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@gtm-support/vue-gtm": "1.2.3", + "@material/material-color-utilities": "0.2.7", "@quasar/extras": "1.10.10", "@tonejs/midi": "2.0.28", "async-lock": "1.4.0", diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts new file mode 100644 index 0000000000..17bb8b8fbb --- /dev/null +++ b/src/helpers/colors.ts @@ -0,0 +1,149 @@ +import { + CorePalette, + TonalPalette, + argbFromHex, + Hct, +} from "@material/material-color-utilities"; + +export interface ColorConfig { + sourceColor: string; + primaryHue: { + light: number; + dark: number; + }; + secondaryHue: { + light: number; + dark: number; + }; + tertiaryHue: { + light: number; + dark: number; + }; + neutral: { + light: number; + dark: number; + }; + neutralVariant: { + light: number; + dark: number; + }; + customColors: { + error: string; + success: string; + }; +} + +export function generateColorPalette(config: ColorConfig) { + const sourceArgb = argbFromHex(config.sourceColor); + const corePalette = CorePalette.of(sourceArgb); + + // primary, secondary, tertiary の TonalPalette を生成 + const primaryPalette = TonalPalette.fromHueAndChroma( + config.primaryHue.light, + corePalette.a1.chroma, + ); + const secondaryPalette = TonalPalette.fromHueAndChroma( + config.secondaryHue.light, + corePalette.a2.chroma, + ); + const tertiaryPalette = TonalPalette.fromHueAndChroma( + config.tertiaryHue.light, + corePalette.a3.chroma, + ); + + // neutral, neutralVariant の TonalPalette を生成 + const neutralPalette = TonalPalette.fromHct( + Hct.from(corePalette.n1.hue, corePalette.n1.chroma, config.neutral.light), + ); + + const neutralVariantPalette = TonalPalette.fromHct( + Hct.from( + corePalette.n2.hue, + corePalette.n2.chroma, + config.neutralVariant.light, + ), + ); + + // カスタムカラー + const customColors = Object.entries(config.customColors).reduce<{ + [key: string]: number; + }>((acc, [key, value]) => { + acc[key] = argbFromHex(value); + return acc; + }, {}); + + const lightScheme = { + primary: primaryPalette.tone(40), + onPrimary: primaryPalette.tone(100), + primaryContainer: primaryPalette.tone(90), + onPrimaryContainer: primaryPalette.tone(10), + secondary: secondaryPalette.tone(40), + onSecondary: secondaryPalette.tone(100), + secondaryContainer: secondaryPalette.tone(90), + onSecondaryContainer: secondaryPalette.tone(10), + tertiary: tertiaryPalette.tone(40), + onTertiary: tertiaryPalette.tone(100), + tertiaryContainer: tertiaryPalette.tone(90), + onTertiaryContainer: tertiaryPalette.tone(10), + error: corePalette.error.tone(40), + onError: corePalette.error.tone(100), + errorContainer: corePalette.error.tone(90), + onErrorContainer: corePalette.error.tone(10), + background: neutralPalette.tone(99), + onBackground: neutralPalette.tone(10), + surface: neutralPalette.tone(99), + onSurface: neutralPalette.tone(10), + surfaceVariant: neutralVariantPalette.tone(90), + onSurfaceVariant: neutralVariantPalette.tone(30), + outline: neutralVariantPalette.tone(50), + outlineVariant: neutralVariantPalette.tone(80), + shadow: neutralPalette.tone(0), + scrim: neutralPalette.tone(0), + inverseSurface: neutralPalette.tone(20), + inverseOnSurface: neutralPalette.tone(95), + inversePrimary: primaryPalette.tone(80), + }; + + const darkScheme = { + primary: primaryPalette.tone(80), + onPrimary: primaryPalette.tone(20), + primaryContainer: primaryPalette.tone(30), + onPrimaryContainer: primaryPalette.tone(90), + secondary: secondaryPalette.tone(80), + onSecondary: secondaryPalette.tone(20), + secondaryContainer: secondaryPalette.tone(30), + onSecondaryContainer: secondaryPalette.tone(90), + tertiary: tertiaryPalette.tone(80), + onTertiary: tertiaryPalette.tone(20), + tertiaryContainer: tertiaryPalette.tone(30), + onTertiaryContainer: tertiaryPalette.tone(90), + error: corePalette.error.tone(80), + onError: corePalette.error.tone(20), + errorContainer: corePalette.error.tone(30), + onErrorContainer: corePalette.error.tone(90), + background: neutralPalette.tone(10), + onBackground: neutralPalette.tone(90), + surface: neutralPalette.tone(10), + onSurface: neutralPalette.tone(90), + surfaceVariant: neutralVariantPalette.tone(30), + onSurfaceVariant: neutralVariantPalette.tone(80), + outline: neutralVariantPalette.tone(60), + outlineVariant: neutralVariantPalette.tone(30), + shadow: neutralPalette.tone(0), + scrim: neutralPalette.tone(0), + inverseSurface: neutralPalette.tone(90), + inverseOnSurface: neutralPalette.tone(20), + inversePrimary: primaryPalette.tone(40), + }; + + return { + light: { + ...lightScheme, + ...customColors, + }, + dark: { + ...darkScheme, + ...customColors, + }, + }; +} diff --git a/src/main.ts b/src/main.ts index 838b961b58..39e49a9c08 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import iconSet from "quasar/icon-set/material-icons"; import { store, storeKey } from "./store"; import { ipcMessageReceiver } from "./plugins/ipcMessageReceiverPlugin"; import { hotkeyPlugin } from "./plugins/hotkeyPlugin"; +import { generateColorPalette } from "./helpers/colors"; import App from "@/components/App.vue"; import { markdownItPlugin } from "@/plugins/markdownItPlugin"; @@ -16,6 +17,64 @@ import "./styles/_index.scss"; // ため、それを防止するため自前でdataLayerをあらかじめ用意する window.dataLayer = []; +const colorConfig = { + sourceColor: "#a5d4ad", + primaryHue: { + light: 260, + dark: 240, + }, + secondaryHue: { + light: 200, + dark: 180, + }, + tertiaryHue: { + light: 180, + dark: 160, + }, + neutral: { + light: 200, + dark: 800, + }, + neutralVariant: { + light: 200, + dark: 800, + }, + customColors: { + error: "#B3261E", + success: "#198754", + }, +}; + +// カラーパレットを生成 +const colorPalette = generateColorPalette(colorConfig); + +// CSS 変数に設定 +function applyColorPalette(palette: { [key: string]: number }) { + const root = document.documentElement; + for (const [key, value] of Object.entries(palette)) { + root.style.setProperty( + `--md-sys-color-${key}`, + `#${value.toString(16).padStart(8, "0").slice(2)}`, + ); + } +} + +// ライトテーマとダークテーマの切り替え +function applyTheme(isDark: boolean) { + const palette = isDark ? colorPalette.dark : colorPalette.light; + applyColorPalette(palette); +} + +// 初期テーマを適用 +applyTheme(window.matchMedia("(prefers-color-scheme: dark)").matches); + +// テーマ切り替えイベントリスナー +window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", (event) => { + applyTheme(event.matches); +}); + createApp(App) .use(store, storeKey) .use( diff --git a/src/styles/fonts.scss b/src/styles/fonts.scss index 2911455b92..07c4d2d40d 100644 --- a/src/styles/fonts.scss +++ b/src/styles/fonts.scss @@ -5,6 +5,13 @@ font-weight: normal; } +@font-face { + font-family: "Unhinted Rounded M+ 1p"; + src: url("../fonts/unhinted-rounded-mplus-1p-medium.woff2"); + + font-weight: medium; +} + @font-face { font-family: "Unhinted Rounded M+ 1p"; src: url("../fonts/unhinted-rounded-mplus-1p-bold.woff2"); From 1fb7f9220297602719bf5c4faa3965971f7fc3bd Mon Sep 17 00:00:00 2001 From: Romot Date: Mon, 17 Jun 2024 21:55:23 +0900 Subject: [PATCH 02/37] =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=BC=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=9E(=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ScoreSequencer.vue | 29 ++- src/components/Sing/SequencerKeys.vue | 16 +- src/components/Sing/SequencerNote.vue | 46 ++-- src/components/Sing/SequencerRuler.vue | 25 +- src/components/Sing/ToolBar/ToolBar.vue | 108 +++++---- src/helpers/colors.ts | 292 +++++++++++++++--------- src/main.ts | 59 ++--- src/styles/_index.scss | 1 + src/styles/tokens.scss | 0 9 files changed, 328 insertions(+), 248 deletions(-) create mode 100644 src/styles/tokens.scss diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 991294f395..6f199a6059 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -1575,16 +1575,14 @@ const contextMenuData = ref([ .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-variant); } .sequencer-ruler { @@ -1616,35 +1614,36 @@ const contextMenuData = ref([ .sequencer-grid-cell { display: block; - stroke: rgba(colors.$sequencer-sub-divider-rgb, 0.3); + stroke: var(--md-sys-color-surface-variant); stroke-width: 1; } .sequencer-grid-octave-cell { - stroke: colors.$sequencer-main-divider; + stroke: var(--md-sys-color-outline); } .sequencer-grid-octave-line { backface-visibility: hidden; - stroke: colors.$sequencer-main-divider; + stroke: var(--md-sys-color-outline); } .sequencer-grid-cell-white { - fill: colors.$sequencer-whitekey-cell; + fill: var(--md-sys-color-background); } .sequencer-grid-cell-black { - fill: colors.$sequencer-blackkey-cell; + fill: var(--md-sys-color-surface-variant); } .sequencer-grid-measure-line { backface-visibility: hidden; - stroke: colors.$sequencer-main-divider; + stroke: var(--md-sys-color-outline); } .sequencer-grid-beat-line { backface-visibility: hidden; - stroke: colors.$sequencer-sub-divider; + stroke: var(--md-sys-color-outline); + opacity: 0.6; } .sequencer-guideline { @@ -1652,7 +1651,7 @@ const contextMenuData = ref([ top: 0; left: -1px; width: 2px; - background: hsl(130, 35%, 82%); + background: var(--md-sys-color-secondary-container); pointer-events: none; } @@ -1683,15 +1682,15 @@ const contextMenuData = ref([ 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 { diff --git a/src/components/Sing/SequencerKeys.vue b/src/components/Sing/SequencerKeys.vue index 44405d90bc..4294a40151 100644 --- a/src/components/Sing/SequencerKeys.vue +++ b/src/components/Sing/SequencerKeys.vue @@ -175,29 +175,29 @@ onUnmounted(() => { .sequencer-keys { backface-visibility: hidden; - background: colors.$background; + background: var(--md-sys-color-background); overflow: hidden; } .white-key { - fill: colors.$sequencer-white-key; - stroke: colors.$sequencer-main-divider; + fill: var(--md-sys-color-background); + stroke: var(--md-sys-color-outline); } .white-key-being-pressed { - fill: colors.$primary; - stroke: colors.$primary; + fill: var(--md-sys-color-secondary-container); + stroke: var(--md-sys-color-primary); } .black-key { - fill: colors.$sequencer-black-key; + fill: var(--md-sys-color-surface-container-highest); } .black-key-being-pressed { - fill: colors.$primary; + fill: var(--md-sys-color-secondary-container); } .pitchname { - fill: colors.$sequencer-black-key; + fill: var(--md-sys-color-on-surface); } diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index 56c13bb0ec..26fce84423 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -302,22 +302,21 @@ const onLyricInput = (event: Event) => { &.below-pitch { .note-bar { - background-color: rgba(colors.$primary-rgb, 0.18); - border-color: hsl(130, 35%, 78%); + background-color: var(--md-sys-color-primary-container); + border-color: var(--md-sys-color-primary); } } &.selected { - // 色は仮 .note-bar { - background-color: lab(95, -22.953, 14.365); - border-color: lab(65, -22.953, 14.365); - outline: solid 2px lab(70, -22.953, 14.365); + background-color: var(--md-sys-color-primary-container); + border-color: var(--md-sys-color-inverse-primary); + outline: solid 2px var(--md-sys-color-inverse-primary); } &.below-pitch { .note-bar { - background-color: rgba(colors.$primary-rgb, 0.18); + background-color: var(--md-sys-color-primary-container); } } } @@ -367,14 +366,9 @@ const onLyricInput = (event: Event) => { min-width: 2rem; padding: 0; background: transparent; - color: #121212; + color: var(--md-sys-color-on-surface); font-size: 1rem; - font-weight: 700; - text-shadow: - -1px -1px 0 #fff, - 1px -1px 0 #fff, - -1px 1px 0 #fff, - 1px 1px 0 #fff; + font-weight: 500; white-space: nowrap; pointer-events: none; } @@ -384,45 +378,45 @@ const onLyricInput = (event: Event) => { position: absolute; width: calc(100% + 1px); height: 100%; - background-color: colors.$primary; - border: 1px solid rgba(colors.$background-rgb, 0.5); + border: 1px solid var(--md-sys-color-brand); + background-color: var(--md-sys-color-secondary-container); border-radius: 4px; } .note-left-edge { position: absolute; top: 0; - left: -1px; - width: 5px; + left: 0; + width: 6px; height: 100%; &:hover { // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する - background-color: lab(80, -22.953, 14.365); + background-color: var(--md-sys-color-inverse-primary); } } .note-right-edge { position: absolute; top: 0; - right: -1px; - width: 5px; + right: 0; + width: 6px; height: 100%; &:hover { // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する - background-color: lab(80, -22.953, 14.365); + background-color: var(--md-sys-color-inverse-primary); } } .note-lyric-input { position: absolute; bottom: 0; - font-weight: 700; - min-width: 3rem; - max-width: 6rem; + font-weight: 500; + min-width: 2rem; + max-width: fit-content; border: 0; - outline: 2px solid lab(80, -22.953, 14.365); + outline: 2px solid var(--md-sys-color-inverse-primary); border-radius: 4px; } diff --git a/src/components/Sing/SequencerRuler.vue b/src/components/Sing/SequencerRuler.vue index 276b940119..dae0f2dc29 100644 --- a/src/components/Sing/SequencerRuler.vue +++ b/src/components/Sing/SequencerRuler.vue @@ -14,9 +14,9 @@ :key="n" :x1="beatWidth * (n - 1)" :x2="beatWidth * (n - 1)" - :y1="n === 1 ? 16 : 24" + :y1="n === 1 ? 20 : 28" y2="100%" - stroke-width="1" + stroke-width="1.5" :class="`sequencer-ruler-${n === 1 ? 'measure' : 'beat'}-line`" /> @@ -25,8 +25,8 @@ v-for="measureInfo in measureInfos" :key="measureInfo.number" font-size="12" - :x="measureInfo.x + 4" - y="20" + :x="measureInfo.x + 8" + y="32" class="sequencer-ruler-measure-number" > {{ measureInfo.number }} @@ -68,7 +68,7 @@ const props = withDefaults( ); const store = useStore(); const state = store.state; -const height = ref(32); +const height = ref(40); const playheadTicks = ref(0); const tpqn = computed(() => state.tpqn); const timeSignatures = computed(() => state.timeSignatures); @@ -188,28 +188,19 @@ onUnmounted(() => { @use "@/styles/colors" as colors; .sequencer-ruler { - background: colors.$background; + background: var(--md-sys-color-surface-variant); + height: 40px; position: relative; overflow: hidden; } -.sequencer-ruler-border-bottom { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - border-top: 1px solid colors.$sequencer-sub-divider; - border-bottom: 1px solid colors.$sequencer-sub-divider; -} - .sequencer-ruler-playhead { position: absolute; top: 0; left: -1px; width: 2px; height: 100%; - background: rgba(colors.$display-rgb, 0.6); + background: var(--md-sys-color-inverse-surface); pointer-events: none; will-change: transform; } diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue index e18b511ce2..17292f0b71 100644 --- a/src/components/Sing/ToolBar/ToolBar.vue +++ b/src/components/Sing/ToolBar/ToolBar.vue @@ -37,29 +37,37 @@ -
- -
/
- -
+ +
+ +
/
+ +
+
@@ -219,6 +227,20 @@ const volumeRangeAdjustment = computed( () => 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 +288,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 = ( @@ -451,12 +483,12 @@ onUnmounted(() => { } .sing-toolbar { - background: colors.$sing-toolbar; + background: var(--md-sys-color-surface); align-items: center; display: flex; justify-content: space-between; - min-height: 56px; - padding: 0 8px 0 0; + min-height: 64px; + padding: 0 16px; width: 100%; } @@ -476,13 +508,13 @@ onUnmounted(() => { .key-range-adjustment { margin-left: 16px; margin-right: 4px; - width: 50px; + width: 48px; } .volume-range-adjustment { margin-left: 4px; margin-right: 4px; - width: 50px; + width: 48px; } .sing-tempo { @@ -492,7 +524,6 @@ onUnmounted(() => { } .sing-tempo-icon { - color: rgba(colors.$display-rgb, 0.6); padding-right: 0px; position: relative; top: 4px; @@ -512,7 +543,6 @@ onUnmounted(() => { width: 32px; } .sing-beats-separator { - color: rgba(colors.$display-rgb, 0.6); position: relative; top: 5px; margin-right: 8px; @@ -532,7 +562,7 @@ onUnmounted(() => { 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 { diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts index 17bb8b8fbb..31ba75887d 100644 --- a/src/helpers/colors.ts +++ b/src/helpers/colors.ts @@ -7,63 +7,87 @@ import { export interface ColorConfig { sourceColor: string; - primaryHue: { - light: number; - dark: number; + neutralHue?: number; + light: { + primary?: string; + secondary?: string; + tertiary?: string; + error?: string; }; - secondaryHue: { - light: number; - dark: number; - }; - tertiaryHue: { - light: number; - dark: number; - }; - neutral: { - light: number; - dark: number; - }; - neutralVariant: { - light: number; - dark: number; + dark: { + primary?: string; + secondary?: string; + tertiary?: string; + error?: string; }; customColors: { - error: string; - success: string; + [key: string]: string; + }; + tones: { + primary?: number; + secondary?: number; + tertiary?: number; + neutral?: number; + neutralVariant?: number; + error?: number; }; } export function generateColorPalette(config: ColorConfig) { const sourceArgb = argbFromHex(config.sourceColor); - const corePalette = CorePalette.of(sourceArgb); - // primary, secondary, tertiary の TonalPalette を生成 - const primaryPalette = TonalPalette.fromHueAndChroma( - config.primaryHue.light, - corePalette.a1.chroma, - ); - const secondaryPalette = TonalPalette.fromHueAndChroma( - config.secondaryHue.light, - corePalette.a2.chroma, - ); - const tertiaryPalette = TonalPalette.fromHueAndChroma( - config.tertiaryHue.light, - corePalette.a3.chroma, - ); - - // neutral, neutralVariant の TonalPalette を生成 - const neutralPalette = TonalPalette.fromHct( - Hct.from(corePalette.n1.hue, corePalette.n1.chroma, config.neutral.light), - ); + // ニュートラルカラーの色相 (デフォルトは 180) + const neutralHue = config.neutralHue ?? 180; + // neutral, neutralVariant の TonalPalette を生成 (light/dark に依存しない) + const neutralPalette = TonalPalette.fromHct(Hct.from(neutralHue, 0, 99)); const neutralVariantPalette = TonalPalette.fromHct( - Hct.from( - corePalette.n2.hue, - corePalette.n2.chroma, - config.neutralVariant.light, - ), + Hct.from(neutralHue, 0, 90), ); + // primaryカラーが指定されている場合は、それをsource colorとしてCorePaletteを生成 + const lightCorePalette = config.light.primary + ? CorePalette.of(argbFromHex(config.light.primary)) + : CorePalette.of(sourceArgb); + const darkCorePalette = config.dark.primary + ? CorePalette.of(argbFromHex(config.dark.primary)) + : CorePalette.of(sourceArgb); + + // ライトテーマのカラーパレット生成 + const lightPrimaryHct = Hct.fromInt(lightCorePalette.a1.tone(40)); + const lightSecondaryHct = config.light.secondary + ? Hct.fromInt(argbFromHex(config.light.secondary)) + : Hct.fromInt(lightCorePalette.a2.tone(40)); // CorePalette から取得 + const lightTertiaryHct = config.light.tertiary + ? Hct.fromInt(argbFromHex(config.light.tertiary)) + : Hct.fromInt(lightCorePalette.a3.tone(40)); // CorePalette から取得 + const lightErrorHct = config.light.error + ? Hct.fromInt(argbFromHex(config.light.error)) + : Hct.from(25, 84, 40); + + // ダークテーマのカラーパレット生成 (lightテーマと同様) + const darkPrimaryHct = Hct.fromInt(darkCorePalette.a1.tone(40)); + const darkSecondaryHct = config.dark.secondary + ? Hct.fromInt(argbFromHex(config.dark.secondary)) + : Hct.fromInt(darkCorePalette.a2.tone(40)); + const darkTertiaryHct = config.dark.tertiary + ? Hct.fromInt(argbFromHex(config.dark.tertiary)) + : Hct.fromInt(darkCorePalette.a3.tone(40)); + const darkErrorHct = config.dark.error + ? Hct.fromInt(argbFromHex(config.dark.error)) + : Hct.from(25, 84, 80); + + // TonalPalette を生成 + const lightPrimaryPalette = TonalPalette.fromHct(lightPrimaryHct); + const lightSecondaryPalette = TonalPalette.fromHct(lightSecondaryHct); + const lightTertiaryPalette = TonalPalette.fromHct(lightTertiaryHct); + const lightErrorPalette = TonalPalette.fromHct(lightErrorHct); + + const darkPrimaryPalette = TonalPalette.fromHct(darkPrimaryHct); + const darkSecondaryPalette = TonalPalette.fromHct(darkSecondaryHct); + const darkTertiaryPalette = TonalPalette.fromHct(darkTertiaryHct); + const darkErrorPalette = TonalPalette.fromHct(darkErrorHct); + // カスタムカラー const customColors = Object.entries(config.customColors).reduce<{ [key: string]: number; @@ -72,68 +96,108 @@ export function generateColorPalette(config: ColorConfig) { return acc; }, {}); + // Light Scheme const lightScheme = { - primary: primaryPalette.tone(40), - onPrimary: primaryPalette.tone(100), - primaryContainer: primaryPalette.tone(90), - onPrimaryContainer: primaryPalette.tone(10), - secondary: secondaryPalette.tone(40), - onSecondary: secondaryPalette.tone(100), - secondaryContainer: secondaryPalette.tone(90), - onSecondaryContainer: secondaryPalette.tone(10), - tertiary: tertiaryPalette.tone(40), - onTertiary: tertiaryPalette.tone(100), - tertiaryContainer: tertiaryPalette.tone(90), - onTertiaryContainer: tertiaryPalette.tone(10), - error: corePalette.error.tone(40), - onError: corePalette.error.tone(100), - errorContainer: corePalette.error.tone(90), - onErrorContainer: corePalette.error.tone(10), - background: neutralPalette.tone(99), - onBackground: neutralPalette.tone(10), - surface: neutralPalette.tone(99), - onSurface: neutralPalette.tone(10), - surfaceVariant: neutralVariantPalette.tone(90), - onSurfaceVariant: neutralVariantPalette.tone(30), - outline: neutralVariantPalette.tone(50), - outlineVariant: neutralVariantPalette.tone(80), - shadow: neutralPalette.tone(0), - scrim: neutralPalette.tone(0), - inverseSurface: neutralPalette.tone(20), - inverseOnSurface: neutralPalette.tone(95), - inversePrimary: primaryPalette.tone(80), + primary: lightPrimaryPalette.tone(40), + onPrimary: lightPrimaryPalette.tone(100), + primaryContainer: lightPrimaryPalette.tone(90), + onPrimaryContainer: lightPrimaryPalette.tone(10), + secondary: lightSecondaryPalette.tone(40), + onSecondary: lightSecondaryPalette.tone(100), + secondaryContainer: lightSecondaryPalette.tone(90), + onSecondaryContainer: lightSecondaryPalette.tone(10), + tertiary: lightTertiaryPalette.tone(40), + onTertiary: lightTertiaryPalette.tone(100), + tertiaryContainer: lightTertiaryPalette.tone(90), + onTertiaryContainer: lightTertiaryPalette.tone(10), + error: lightErrorPalette.tone(40), + onError: lightErrorPalette.tone(100), + errorContainer: lightErrorPalette.tone(90), + onErrorContainer: lightErrorPalette.tone(10), + background: neutralPalette.tone(100), // neutralPalette を使用 + onBackground: neutralPalette.tone(10), // neutralPalette を使用 + surface: neutralPalette.tone(99), // neutralPalette を使用 + onSurface: neutralPalette.tone(10), // neutralPalette を使用 + surfaceVariant: neutralVariantPalette.tone(95), // neutralVariantPalette を使用 + onSurfaceVariant: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 + outline: neutralVariantPalette.tone(50), // neutralVariantPalette を使用 + outlineVariant: neutralVariantPalette.tone(80), // neutralVariantPalette を使用 + shadow: neutralPalette.tone(0), // neutralPalette を使用 + scrim: neutralPalette.tone(0), // neutralPalette を使用 + inverseSurface: neutralPalette.tone(20), // neutralPalette を使用 + inverseOnSurface: neutralPalette.tone(95), // neutralPalette を使用 + inversePrimary: lightPrimaryPalette.tone(80), + + // Surface container + surfaceContainerLowest: neutralVariantPalette.tone(90), // neutralVariantPalette を使用 + surfaceContainerLow: neutralVariantPalette.tone(80), // neutralVariantPalette を使用 + surfaceContainer: neutralVariantPalette.tone(70), // neutralVariantPalette を使用 + surfaceContainerHigh: neutralVariantPalette.tone(60), // neutralVariantPalette を使用 + surfaceContainerHighest: neutralVariantPalette.tone(50), // neutralVariantPalette を使用 + + // Fixed Colors + surfaceTint: lightPrimaryPalette.tone(40), + primaryFixed: lightPrimaryPalette.tone(90), + onPrimaryFixed: lightPrimaryPalette.tone(10), + primaryFixedDim: lightPrimaryPalette.tone(70), + secondaryFixed: lightSecondaryPalette.tone(90), + onSecondaryFixed: lightSecondaryPalette.tone(10), + secondaryFixedDim: lightSecondaryPalette.tone(70), + tertiaryFixed: lightTertiaryPalette.tone(90), + onTertiaryFixed: lightTertiaryPalette.tone(10), + tertiaryFixedDim: lightTertiaryPalette.tone(70), }; + // Dark Scheme const darkScheme = { - primary: primaryPalette.tone(80), - onPrimary: primaryPalette.tone(20), - primaryContainer: primaryPalette.tone(30), - onPrimaryContainer: primaryPalette.tone(90), - secondary: secondaryPalette.tone(80), - onSecondary: secondaryPalette.tone(20), - secondaryContainer: secondaryPalette.tone(30), - onSecondaryContainer: secondaryPalette.tone(90), - tertiary: tertiaryPalette.tone(80), - onTertiary: tertiaryPalette.tone(20), - tertiaryContainer: tertiaryPalette.tone(30), - onTertiaryContainer: tertiaryPalette.tone(90), - error: corePalette.error.tone(80), - onError: corePalette.error.tone(20), - errorContainer: corePalette.error.tone(30), - onErrorContainer: corePalette.error.tone(90), - background: neutralPalette.tone(10), - onBackground: neutralPalette.tone(90), - surface: neutralPalette.tone(10), - onSurface: neutralPalette.tone(90), - surfaceVariant: neutralVariantPalette.tone(30), - onSurfaceVariant: neutralVariantPalette.tone(80), - outline: neutralVariantPalette.tone(60), - outlineVariant: neutralVariantPalette.tone(30), - shadow: neutralPalette.tone(0), - scrim: neutralPalette.tone(0), - inverseSurface: neutralPalette.tone(90), - inverseOnSurface: neutralPalette.tone(20), - inversePrimary: primaryPalette.tone(40), + primary: darkPrimaryPalette.tone(80), + onPrimary: darkPrimaryPalette.tone(20), + primaryContainer: darkPrimaryPalette.tone(30), + onPrimaryContainer: darkPrimaryPalette.tone(90), + secondary: darkSecondaryPalette.tone(80), + onSecondary: darkSecondaryPalette.tone(20), + secondaryContainer: darkSecondaryPalette.tone(30), + onSecondaryContainer: darkSecondaryPalette.tone(90), + tertiary: darkTertiaryPalette.tone(80), + onTertiary: darkTertiaryPalette.tone(20), + tertiaryContainer: darkTertiaryPalette.tone(30), + onTertiaryContainer: darkTertiaryPalette.tone(90), + error: darkErrorPalette.tone(80), + onError: darkErrorPalette.tone(20), + errorContainer: darkErrorPalette.tone(30), + onErrorContainer: darkErrorPalette.tone(90), + background: neutralPalette.tone(10), // neutralPalette を使用 + onBackground: neutralPalette.tone(90), // neutralPalette を使用 + surface: neutralPalette.tone(10), // neutralPalette を使用 + onSurface: neutralPalette.tone(90), // neutralPalette を使用 + surfaceVariant: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 + onSurfaceVariant: neutralVariantPalette.tone(80), // neutralVariantPalette を使用 + outline: neutralVariantPalette.tone(60), // neutralVariantPalette を使用 + outlineVariant: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 + shadow: neutralPalette.tone(0), // neutralPalette を使用 + scrim: neutralPalette.tone(0), // neutralPalette を使用 + inverseSurface: neutralPalette.tone(90), // neutralPalette を使用 + inverseOnSurface: neutralPalette.tone(20), // neutralPalette を使用 + inversePrimary: darkPrimaryPalette.tone(40), + + // Surface container + surfaceContainerLowest: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 + surfaceContainerLow: neutralVariantPalette.tone(40), // neutralVariantPalette を使用 + surfaceContainer: neutralVariantPalette.tone(50), // neutralVariantPalette を使用 + surfaceContainerHigh: neutralVariantPalette.tone(60), // neutralVariantPalette を使用 + surfaceContainerHighest: neutralVariantPalette.tone(70), // neutralVariantPalette を使用 + + // Fixed Colors + surfaceTint: darkPrimaryPalette.tone(80), + primaryFixed: darkPrimaryPalette.tone(40), + onPrimaryFixed: darkPrimaryPalette.tone(90), + primaryFixedDim: darkPrimaryPalette.tone(60), + secondaryFixed: darkSecondaryPalette.tone(40), + onSecondaryFixed: darkSecondaryPalette.tone(90), + secondaryFixedDim: darkSecondaryPalette.tone(60), + tertiaryFixed: darkTertiaryPalette.tone(40), + onTertiaryFixed: darkTertiaryPalette.tone(90), + tertiaryFixedDim: darkTertiaryPalette.tone(60), }; return { @@ -147,3 +211,27 @@ export function generateColorPalette(config: ColorConfig) { }, }; } + +// CSS 変数に設定 +export function applyColorPalette(palette: { [key: string]: number }) { + const root = document.documentElement; + for (const [key, value] of Object.entries(palette)) { + const kebabCaseKey = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + root.style.setProperty( + `--md-sys-color-${kebabCaseKey}`, + `#${value.toString(16).padStart(8, "0").slice(2)}`, + ); + } +} + +// ライトテーマとダークテーマの切り替え +export function applyTheme( + colorPalette: { + light: { [key: string]: number }; + dark: { [key: string]: number }; + }, + isDark: boolean, +) { + const palette = isDark ? colorPalette.dark : colorPalette.light; + applyColorPalette(palette); +} diff --git a/src/main.ts b/src/main.ts index 39e49a9c08..57dcf9e26b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import iconSet from "quasar/icon-set/material-icons"; import { store, storeKey } from "./store"; import { ipcMessageReceiver } from "./plugins/ipcMessageReceiverPlugin"; import { hotkeyPlugin } from "./plugins/hotkeyPlugin"; -import { generateColorPalette } from "./helpers/colors"; +import { generateColorPalette, applyTheme } from "./helpers/colors"; import App from "@/components/App.vue"; import { markdownItPlugin } from "@/plugins/markdownItPlugin"; @@ -19,61 +19,38 @@ window.dataLayer = []; const colorConfig = { sourceColor: "#a5d4ad", - primaryHue: { - light: 260, - dark: 240, - }, - secondaryHue: { - light: 200, - dark: 180, - }, - tertiaryHue: { - light: 180, - dark: 160, - }, - neutral: { - light: 200, - dark: 800, - }, - neutralVariant: { - light: 200, - dark: 800, + neutralHue: 180, + light: {}, + dark: {}, + tones: { + primary: 40, + secondary: 40, + tertiary: 40, + neutral: 40, + neutralVariant: 40, + error: 40, }, customColors: { error: "#B3261E", - success: "#198754", + brand: "#a5d4ad", }, }; // カラーパレットを生成 const colorPalette = generateColorPalette(colorConfig); -// CSS 変数に設定 -function applyColorPalette(palette: { [key: string]: number }) { - const root = document.documentElement; - for (const [key, value] of Object.entries(palette)) { - root.style.setProperty( - `--md-sys-color-${key}`, - `#${value.toString(16).padStart(8, "0").slice(2)}`, - ); - } -} - -// ライトテーマとダークテーマの切り替え -function applyTheme(isDark: boolean) { - const palette = isDark ? colorPalette.dark : colorPalette.light; - applyColorPalette(palette); -} - // 初期テーマを適用 -applyTheme(window.matchMedia("(prefers-color-scheme: dark)").matches); +applyTheme( + colorPalette, + window.matchMedia("(prefers-color-scheme: dark)").matches, +); // テーマ切り替えイベントリスナー window .matchMedia("(prefers-color-scheme: dark)") .addEventListener("change", (event) => { - applyTheme(event.matches); -}); + applyTheme(colorPalette, event.matches); + }); createApp(App) .use(store, storeKey) diff --git a/src/styles/_index.scss b/src/styles/_index.scss index 02912d5603..e69f44e2d7 100644 --- a/src/styles/_index.scss +++ b/src/styles/_index.scss @@ -1,5 +1,6 @@ @use './variables' as vars; @use './colors' as colors; +@use './tokens' as tokens; @import "./fonts"; // 優先度を強引に上げる diff --git a/src/styles/tokens.scss b/src/styles/tokens.scss new file mode 100644 index 0000000000..e69de29bb2 From 2ba4376162ed695a0ebe6f46c652a0496b37a3a6 Mon Sep 17 00:00:00 2001 From: Romot Date: Sun, 30 Jun 2024 23:58:39 +0900 Subject: [PATCH 03/37] =?UTF-8?q?=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ScoreSequencer.vue | 15 +- src/components/Sing/SequencerKeys.vue | 6 +- src/components/Sing/SequencerNote.vue | 2 +- src/components/Sing/SequencerRuler.vue | 2 +- src/components/Sing/ToolBar/ToolBar.vue | 4 +- src/helpers/colors.ts | 343 +++++++++--------------- src/main.ts | 103 ++++--- src/styles/colors.scss | 12 - 8 files changed, 205 insertions(+), 282 deletions(-) diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 6f199a6059..b191cf9610 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -1614,8 +1614,8 @@ const contextMenuData = ref([ .sequencer-grid-cell { display: block; - stroke: var(--md-sys-color-surface-variant); - stroke-width: 1; + stroke: var(--md-sys-color-outline); + stroke-width: 0; } .sequencer-grid-octave-cell { @@ -1628,22 +1628,21 @@ const contextMenuData = ref([ } .sequencer-grid-cell-white { - fill: var(--md-sys-color-background); + fill: var(--md-custom-color-cell-white); } .sequencer-grid-cell-black { - fill: var(--md-sys-color-surface-variant); + fill: var(--md-custom-color-cell-black); } .sequencer-grid-measure-line { backface-visibility: hidden; - stroke: var(--md-sys-color-outline); + stroke: var(--md-custom-color-sing-grid-measure-line); } .sequencer-grid-beat-line { backface-visibility: hidden; - stroke: var(--md-sys-color-outline); - opacity: 0.6; + stroke: var(--md-custom-color-sing-grid-beat-line); } .sequencer-guideline { @@ -1690,7 +1689,7 @@ const contextMenuData = ref([ pointer-events: none; position: absolute; border: 1px dashed var(--md-sys-color-secondary); - background: var(--md-sys-color-secondary-container); + background: rgba(var(--md-sys-color-secondary-container), 0.5); } .cursor-draw { diff --git a/src/components/Sing/SequencerKeys.vue b/src/components/Sing/SequencerKeys.vue index 4294a40151..75029d6abd 100644 --- a/src/components/Sing/SequencerKeys.vue +++ b/src/components/Sing/SequencerKeys.vue @@ -180,8 +180,8 @@ onUnmounted(() => { } .white-key { - fill: var(--md-sys-color-background); - stroke: var(--md-sys-color-outline); + fill: var(--md-custom-color-sing-piano-key-white); + stroke: var(--md-custom-color-sing-grid-beat-line); } .white-key-being-pressed { @@ -190,7 +190,7 @@ onUnmounted(() => { } .black-key { - fill: var(--md-sys-color-surface-container-highest); + fill: var(--md-custom-color-sing-piano-key-black); } .black-key-being-pressed { diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index 26fce84423..6795f4b406 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -378,7 +378,7 @@ const onLyricInput = (event: Event) => { position: absolute; width: calc(100% + 1px); height: 100%; - border: 1px solid var(--md-sys-color-brand); + border: 1px solid var(--md-sys-color-secondary); background-color: var(--md-sys-color-secondary-container); border-radius: 4px; } diff --git a/src/components/Sing/SequencerRuler.vue b/src/components/Sing/SequencerRuler.vue index dae0f2dc29..55e3d967a7 100644 --- a/src/components/Sing/SequencerRuler.vue +++ b/src/components/Sing/SequencerRuler.vue @@ -188,7 +188,7 @@ onUnmounted(() => { @use "@/styles/colors" as colors; .sequencer-ruler { - background: var(--md-sys-color-surface-variant); + background: var(--md-custom-color-sing-ruler); height: 40px; position: relative; overflow: hidden; diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue index 17292f0b71..ae085d0095 100644 --- a/src/components/Sing/ToolBar/ToolBar.vue +++ b/src/components/Sing/ToolBar/ToolBar.vue @@ -483,7 +483,7 @@ onUnmounted(() => { } .sing-toolbar { - background: var(--md-sys-color-surface); + background: var(--md-custom-color-sing-toolbar); align-items: center; display: flex; justify-content: space-between; @@ -555,7 +555,7 @@ onUnmounted(() => { font-size: 28px; font-weight: 700; margin-left: 16px; - color: colors.$display; + color: var(--md-sys-color-on-surface); } .sing-playhead-position-millisec { diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts index 31ba75887d..8a4835b05f 100644 --- a/src/helpers/colors.ts +++ b/src/helpers/colors.ts @@ -1,237 +1,134 @@ import { - CorePalette, - TonalPalette, argbFromHex, + hexFromArgb, + themeFromSourceColor, + Theme, + Scheme, + TonalPalette, + CustomColorGroup, Hct, } from "@material/material-color-utilities"; -export interface ColorConfig { - sourceColor: string; - neutralHue?: number; - light: { - primary?: string; - secondary?: string; - tertiary?: string; - error?: string; - }; - dark: { - primary?: string; - secondary?: string; - tertiary?: string; - error?: string; - }; - customColors: { - [key: string]: string; - }; - tones: { - primary?: number; - secondary?: number; - tertiary?: number; - neutral?: number; - neutralVariant?: number; - error?: number; - }; -} - -export function generateColorPalette(config: ColorConfig) { - const sourceArgb = argbFromHex(config.sourceColor); - - // ニュートラルカラーの色相 (デフォルトは 180) - const neutralHue = config.neutralHue ?? 180; - - // neutral, neutralVariant の TonalPalette を生成 (light/dark に依存しない) - const neutralPalette = TonalPalette.fromHct(Hct.from(neutralHue, 0, 99)); - const neutralVariantPalette = TonalPalette.fromHct( - Hct.from(neutralHue, 0, 90), - ); - - // primaryカラーが指定されている場合は、それをsource colorとしてCorePaletteを生成 - const lightCorePalette = config.light.primary - ? CorePalette.of(argbFromHex(config.light.primary)) - : CorePalette.of(sourceArgb); - const darkCorePalette = config.dark.primary - ? CorePalette.of(argbFromHex(config.dark.primary)) - : CorePalette.of(sourceArgb); - - // ライトテーマのカラーパレット生成 - const lightPrimaryHct = Hct.fromInt(lightCorePalette.a1.tone(40)); - const lightSecondaryHct = config.light.secondary - ? Hct.fromInt(argbFromHex(config.light.secondary)) - : Hct.fromInt(lightCorePalette.a2.tone(40)); // CorePalette から取得 - const lightTertiaryHct = config.light.tertiary - ? Hct.fromInt(argbFromHex(config.light.tertiary)) - : Hct.fromInt(lightCorePalette.a3.tone(40)); // CorePalette から取得 - const lightErrorHct = config.light.error - ? Hct.fromInt(argbFromHex(config.light.error)) - : Hct.from(25, 84, 40); - - // ダークテーマのカラーパレット生成 (lightテーマと同様) - const darkPrimaryHct = Hct.fromInt(darkCorePalette.a1.tone(40)); - const darkSecondaryHct = config.dark.secondary - ? Hct.fromInt(argbFromHex(config.dark.secondary)) - : Hct.fromInt(darkCorePalette.a2.tone(40)); - const darkTertiaryHct = config.dark.tertiary - ? Hct.fromInt(argbFromHex(config.dark.tertiary)) - : Hct.fromInt(darkCorePalette.a3.tone(40)); - const darkErrorHct = config.dark.error - ? Hct.fromInt(argbFromHex(config.dark.error)) - : Hct.from(25, 84, 80); - - // TonalPalette を生成 - const lightPrimaryPalette = TonalPalette.fromHct(lightPrimaryHct); - const lightSecondaryPalette = TonalPalette.fromHct(lightSecondaryHct); - const lightTertiaryPalette = TonalPalette.fromHct(lightTertiaryHct); - const lightErrorPalette = TonalPalette.fromHct(lightErrorHct); - - const darkPrimaryPalette = TonalPalette.fromHct(darkPrimaryHct); - const darkSecondaryPalette = TonalPalette.fromHct(darkSecondaryHct); - const darkTertiaryPalette = TonalPalette.fromHct(darkTertiaryHct); - const darkErrorPalette = TonalPalette.fromHct(darkErrorHct); - - // カスタムカラー - const customColors = Object.entries(config.customColors).reduce<{ - [key: string]: number; - }>((acc, [key, value]) => { - acc[key] = argbFromHex(value); - return acc; - }, {}); - - // Light Scheme - const lightScheme = { - primary: lightPrimaryPalette.tone(40), - onPrimary: lightPrimaryPalette.tone(100), - primaryContainer: lightPrimaryPalette.tone(90), - onPrimaryContainer: lightPrimaryPalette.tone(10), - secondary: lightSecondaryPalette.tone(40), - onSecondary: lightSecondaryPalette.tone(100), - secondaryContainer: lightSecondaryPalette.tone(90), - onSecondaryContainer: lightSecondaryPalette.tone(10), - tertiary: lightTertiaryPalette.tone(40), - onTertiary: lightTertiaryPalette.tone(100), - tertiaryContainer: lightTertiaryPalette.tone(90), - onTertiaryContainer: lightTertiaryPalette.tone(10), - error: lightErrorPalette.tone(40), - onError: lightErrorPalette.tone(100), - errorContainer: lightErrorPalette.tone(90), - onErrorContainer: lightErrorPalette.tone(10), - background: neutralPalette.tone(100), // neutralPalette を使用 - onBackground: neutralPalette.tone(10), // neutralPalette を使用 - surface: neutralPalette.tone(99), // neutralPalette を使用 - onSurface: neutralPalette.tone(10), // neutralPalette を使用 - surfaceVariant: neutralVariantPalette.tone(95), // neutralVariantPalette を使用 - onSurfaceVariant: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 - outline: neutralVariantPalette.tone(50), // neutralVariantPalette を使用 - outlineVariant: neutralVariantPalette.tone(80), // neutralVariantPalette を使用 - shadow: neutralPalette.tone(0), // neutralPalette を使用 - scrim: neutralPalette.tone(0), // neutralPalette を使用 - inverseSurface: neutralPalette.tone(20), // neutralPalette を使用 - inverseOnSurface: neutralPalette.tone(95), // neutralPalette を使用 - inversePrimary: lightPrimaryPalette.tone(80), - - // Surface container - surfaceContainerLowest: neutralVariantPalette.tone(90), // neutralVariantPalette を使用 - surfaceContainerLow: neutralVariantPalette.tone(80), // neutralVariantPalette を使用 - surfaceContainer: neutralVariantPalette.tone(70), // neutralVariantPalette を使用 - surfaceContainerHigh: neutralVariantPalette.tone(60), // neutralVariantPalette を使用 - surfaceContainerHighest: neutralVariantPalette.tone(50), // neutralVariantPalette を使用 - - // Fixed Colors - surfaceTint: lightPrimaryPalette.tone(40), - primaryFixed: lightPrimaryPalette.tone(90), - onPrimaryFixed: lightPrimaryPalette.tone(10), - primaryFixedDim: lightPrimaryPalette.tone(70), - secondaryFixed: lightSecondaryPalette.tone(90), - onSecondaryFixed: lightSecondaryPalette.tone(10), - secondaryFixedDim: lightSecondaryPalette.tone(70), - tertiaryFixed: lightTertiaryPalette.tone(90), - onTertiaryFixed: lightTertiaryPalette.tone(10), - tertiaryFixedDim: lightTertiaryPalette.tone(70), - }; - - // Dark Scheme - const darkScheme = { - primary: darkPrimaryPalette.tone(80), - onPrimary: darkPrimaryPalette.tone(20), - primaryContainer: darkPrimaryPalette.tone(30), - onPrimaryContainer: darkPrimaryPalette.tone(90), - secondary: darkSecondaryPalette.tone(80), - onSecondary: darkSecondaryPalette.tone(20), - secondaryContainer: darkSecondaryPalette.tone(30), - onSecondaryContainer: darkSecondaryPalette.tone(90), - tertiary: darkTertiaryPalette.tone(80), - onTertiary: darkTertiaryPalette.tone(20), - tertiaryContainer: darkTertiaryPalette.tone(30), - onTertiaryContainer: darkTertiaryPalette.tone(90), - error: darkErrorPalette.tone(80), - onError: darkErrorPalette.tone(20), - errorContainer: darkErrorPalette.tone(30), - onErrorContainer: darkErrorPalette.tone(90), - background: neutralPalette.tone(10), // neutralPalette を使用 - onBackground: neutralPalette.tone(90), // neutralPalette を使用 - surface: neutralPalette.tone(10), // neutralPalette を使用 - onSurface: neutralPalette.tone(90), // neutralPalette を使用 - surfaceVariant: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 - onSurfaceVariant: neutralVariantPalette.tone(80), // neutralVariantPalette を使用 - outline: neutralVariantPalette.tone(60), // neutralVariantPalette を使用 - outlineVariant: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 - shadow: neutralPalette.tone(0), // neutralPalette を使用 - scrim: neutralPalette.tone(0), // neutralPalette を使用 - inverseSurface: neutralPalette.tone(90), // neutralPalette を使用 - inverseOnSurface: neutralPalette.tone(20), // neutralPalette を使用 - inversePrimary: darkPrimaryPalette.tone(40), - - // Surface container - surfaceContainerLowest: neutralVariantPalette.tone(30), // neutralVariantPalette を使用 - surfaceContainerLow: neutralVariantPalette.tone(40), // neutralVariantPalette を使用 - surfaceContainer: neutralVariantPalette.tone(50), // neutralVariantPalette を使用 - surfaceContainerHigh: neutralVariantPalette.tone(60), // neutralVariantPalette を使用 - surfaceContainerHighest: neutralVariantPalette.tone(70), // neutralVariantPalette を使用 - - // Fixed Colors - surfaceTint: darkPrimaryPalette.tone(80), - primaryFixed: darkPrimaryPalette.tone(40), - onPrimaryFixed: darkPrimaryPalette.tone(90), - primaryFixedDim: darkPrimaryPalette.tone(60), - secondaryFixed: darkSecondaryPalette.tone(40), - onSecondaryFixed: darkSecondaryPalette.tone(90), - secondaryFixedDim: darkSecondaryPalette.tone(60), - tertiaryFixed: darkTertiaryPalette.tone(40), - onTertiaryFixed: darkTertiaryPalette.tone(90), - tertiaryFixedDim: darkTertiaryPalette.tone(60), +type ColorAdjustment = Partial<{ hue: number; chroma: number; tone: number }>; +type PaletteAdjustments = Partial< + Record +>; +type CustomColor = { + name: string; + palette: keyof Theme["palettes"]; + lightTone: number; + darkTone: number; + blend: boolean; +}; + +export function generateTheme( + sourceColor: string, + adjustments: PaletteAdjustments = {}, + customColors: CustomColor[] = [], +): Theme { + let theme = themeFromSourceColor(argbFromHex(sourceColor)); + + // Apply adjustments to palettes + const adjustedPalettes: Record = { + ...theme.palettes, }; + for (const [key, adjustment] of Object.entries(adjustments)) { + if (key in adjustedPalettes && adjustment) { + const palette = adjustedPalettes[key as keyof Theme["palettes"]]; + const keyHct = Hct.fromInt(palette.keyColor.toInt()); + const newHue = adjustment.hue ?? keyHct.hue; + const newChroma = adjustment.chroma ?? keyHct.chroma; + adjustedPalettes[key as keyof Theme["palettes"]] = + TonalPalette.fromHueAndChroma(newHue, newChroma); + } + } - return { - light: { - ...lightScheme, - ...customColors, - }, - dark: { - ...darkScheme, - ...customColors, + // Process custom colors + const customColorGroups: CustomColorGroup[] = customColors.map((color) => { + const lightColor = adjustedPalettes[color.palette].tone(color.lightTone); + const darkColor = adjustedPalettes[color.palette].tone(color.darkTone); + return { + name: color.name, + value: lightColor, // デフォルト値として明るい方の色を使用 + blend: color.blend, + color: { + name: color.name, + value: lightColor, + blend: color.blend, + }, + light: { + color: lightColor, + onColor: 0, + colorContainer: 0, + onColorContainer: 0, + }, + dark: { + color: darkColor, + onColor: 0, + colorContainer: 0, + onColorContainer: 0, + }, + }; + }); + + // Generate new schemes based on adjusted palettes + const lightScheme = Scheme.light(adjustedPalettes.primary.keyColor.toInt()); + const darkScheme = Scheme.dark(adjustedPalettes.primary.keyColor.toInt()); + + // Update custom colors with scheme colors + customColorGroups.forEach((group) => { + group.light.onColor = lightScheme.onPrimary; + group.light.colorContainer = lightScheme.primaryContainer; + group.light.onColorContainer = lightScheme.onPrimaryContainer; + group.dark.onColor = darkScheme.onPrimary; + group.dark.colorContainer = darkScheme.primaryContainer; + group.dark.onColorContainer = darkScheme.onPrimaryContainer; + }); + + // Create new theme with adjusted palettes, schemes, and custom colors + theme = { + ...theme, + palettes: adjustedPalettes, + schemes: { + light: lightScheme, + dark: darkScheme, }, + customColors: customColorGroups, }; -} -// CSS 変数に設定 -export function applyColorPalette(palette: { [key: string]: number }) { - const root = document.documentElement; - for (const [key, value] of Object.entries(palette)) { - const kebabCaseKey = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); - root.style.setProperty( - `--md-sys-color-${kebabCaseKey}`, - `#${value.toString(16).padStart(8, "0").slice(2)}`, - ); - } + return theme; } -// ライトテーマとダークテーマの切り替え -export function applyTheme( - colorPalette: { - light: { [key: string]: number }; - dark: { [key: string]: number }; - }, +export function themeToCssVariables( + theme: Theme, isDark: boolean, -) { - const palette = isDark ? colorPalette.dark : colorPalette.light; - applyColorPalette(palette); +): Record { + const vars: Record = {}; + + const scheme = isDark ? theme.schemes.dark : theme.schemes.light; + + // Set sys colors + Object.entries(scheme.toJSON()).forEach(([colorName, color]) => { + const kebabCaseName = colorName + .replace(/([a-z])([A-Z])/g, "$1-$2") + .toLowerCase(); + vars[`--md-sys-color-${kebabCaseName}`] = hexFromArgb(color); + }); + + // Set ref palette + for (const [paletteName, palette] of Object.entries(theme.palettes)) { + const tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100]; + for (const tone of tones) { + vars[`--md-ref-palette-${paletteName.toLowerCase()}-${tone}`] = + hexFromArgb(palette.tone(tone)); + } + } + + // Set custom colors + theme.customColors.forEach((customColor) => { + const color = isDark ? customColor.dark.color : customColor.light.color; + vars[`--md-custom-color-${customColor.color.name}`] = hexFromArgb(color); + }); + + return vars; } diff --git a/src/main.ts b/src/main.ts index 57dcf9e26b..01453b9e5e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import iconSet from "quasar/icon-set/material-icons"; import { store, storeKey } from "./store"; import { ipcMessageReceiver } from "./plugins/ipcMessageReceiverPlugin"; import { hotkeyPlugin } from "./plugins/hotkeyPlugin"; -import { generateColorPalette, applyTheme } from "./helpers/colors"; +import { generateTheme, themeToCssVariables } from "./helpers/colors"; import App from "@/components/App.vue"; import { markdownItPlugin } from "@/plugins/markdownItPlugin"; @@ -17,40 +17,79 @@ import "./styles/_index.scss"; // ため、それを防止するため自前でdataLayerをあらかじめ用意する window.dataLayer = []; -const colorConfig = { - sourceColor: "#a5d4ad", - neutralHue: 180, - light: {}, - dark: {}, - tones: { - primary: 40, - secondary: 40, - tertiary: 40, - neutral: 40, - neutralVariant: 40, - error: 40, +// ブランドカラー +const sourceColor = "#A5D4AD"; +// パレットの調整 +const adjustments = { + neutral: { chroma: -5, tone: 0 }, + neutralVariant: { chroma: -1, tone: 0 }, +}; +// カスタムカラー(仮) +const customColors: CustomColor[] = [ + { + name: "sing-toolbar", + palette: "neutral", + lightTone: 99, + darkTone: 20, + blend: true, }, - customColors: { - error: "#B3261E", - brand: "#a5d4ad", + { + name: "sing-ruler", + palette: "neutralVariant", + lightTone: 90, + darkTone: 10, + blend: true, }, -}; - -// カラーパレットを生成 -const colorPalette = generateColorPalette(colorConfig); - -// 初期テーマを適用 -applyTheme( - colorPalette, - window.matchMedia("(prefers-color-scheme: dark)").matches, -); + { + name: "cell-white", + palette: "neutral", + lightTone: 100, + darkTone: 15, + blend: true, + }, + { + name: "cell-black", + palette: "neutral", + lightTone: 96, + darkTone: 12, + blend: true, + }, + { + name: "sing-grid-measure-line", + palette: "neutral", + lightTone: 70, + darkTone: 40, + blend: true, + }, + { + name: "sing-grid-beat-line", + palette: "neutral", + lightTone: 90, + darkTone: 0, + blend: true, + }, + { + name: "sing-piano-key-white", + palette: "neutral", + lightTone: 100, + darkTone: 80, + blend: true, + }, + { + name: "sing-piano-key-black", + palette: "neutral", + lightTone: 50, + darkTone: 30, + blend: true, + }, +]; +const theme = generateTheme(sourceColor, adjustments, customColors); +const cssVariables = themeToCssVariables(theme, false); -// テーマ切り替えイベントリスナー -window - .matchMedia("(prefers-color-scheme: dark)") - .addEventListener("change", (event) => { - applyTheme(colorPalette, event.matches); - }); +// CSSに適用する +Object.entries(cssVariables).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); +}); createApp(App) .use(store, storeKey) 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); From aae725c96861773d3504e4a70536e4bcbc7ec0df Mon Sep 17 00:00:00 2001 From: Romot Date: Mon, 1 Jul 2024 15:21:49 +0900 Subject: [PATCH 04/37] =?UTF-8?q?=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ScoreSequencer.vue | 29 +++++-- src/components/Sing/SequencerNote.vue | 21 +++--- .../Sing/ToolBar/EditTargetSwicher.vue | 68 ++++++++++------- src/components/Sing/ToolBar/ToolBar.vue | 50 ++++++------- src/helpers/colors.ts | 43 ++++++++--- src/main.ts | 74 ------------------ src/store/setting.ts | 75 +++++++++++++++++++ 7 files changed, 208 insertions(+), 152 deletions(-) diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index b191cf9610..a897728c69 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -69,6 +69,15 @@ stroke-width="1" class="sequencer-grid-octave-line" /> + ([ .sequencer-corner { grid-row: 1; grid-column: 1; - background: var(--md-sys-color-surface-variant); + background: var(--md-custom-color-sing-ruler); } .sequencer-ruler { @@ -1614,19 +1623,24 @@ const contextMenuData = ref([ .sequencer-grid-cell { display: block; - stroke: var(--md-sys-color-outline); - stroke-width: 0; + stroke: var(--md-sys-color-grid-beat-line); } .sequencer-grid-octave-cell { - stroke: var(--md-sys-color-outline); + stroke: var(--md-sys-color-grid-measure-line); } .sequencer-grid-octave-line { backface-visibility: hidden; - stroke: var(--md-sys-color-outline); + stroke: var(--md-custom-color-sing-grid-measure-line); + position: relative; + top: 1px; } +.sequencer-grid-f-line { + backface-visibility: hidden; + stroke: var(--md-custom-color-sing-grid-beat-line); +} .sequencer-grid-cell-white { fill: var(--md-custom-color-cell-white); } @@ -1638,6 +1652,7 @@ const contextMenuData = ref([ .sequencer-grid-measure-line { backface-visibility: hidden; stroke: var(--md-custom-color-sing-grid-measure-line); + stroke-width: 1.5; } .sequencer-grid-beat-line { @@ -1648,8 +1663,8 @@ const contextMenuData = ref([ .sequencer-guideline { position: absolute; top: 0; - left: -1px; - width: 2px; + left: 0; + width: 1px; background: var(--md-sys-color-secondary-container); pointer-events: none; } diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index 6795f4b406..ebb5f96ff4 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -309,8 +309,7 @@ const onLyricInput = (event: Event) => { &.selected { .note-bar { - background-color: var(--md-sys-color-primary-container); - border-color: var(--md-sys-color-inverse-primary); + background-color: var(--md-ref-palette-primary-95); outline: solid 2px var(--md-sys-color-inverse-primary); } @@ -366,7 +365,7 @@ const onLyricInput = (event: Event) => { min-width: 2rem; padding: 0; background: transparent; - color: var(--md-sys-color-on-surface); + color: var(--md-ref-palette-neutral-variant-20); font-size: 1rem; font-weight: 500; white-space: nowrap; @@ -378,8 +377,8 @@ const onLyricInput = (event: Event) => { position: absolute; width: calc(100% + 1px); height: 100%; - border: 1px solid var(--md-sys-color-secondary); - background-color: var(--md-sys-color-secondary-container); + border: 1px solid var(--md-ref-palette-neutral-variant-90); + background-color: var(--md-ref-palette-primary-90); border-radius: 4px; } @@ -410,14 +409,18 @@ const onLyricInput = (event: Event) => { } .note-lyric-input { - position: absolute; + position: relative; bottom: 0; font-weight: 500; - min-width: 2rem; - max-width: fit-content; - border: 0; + min-width: fit-content; outline: 2px solid var(--md-sys-color-inverse-primary); border-radius: 4px; + border: 0; + // boxshadow beautiful md3 medium + box-shadow: + 0px 2px 4px -2px rgba(0, 0, 0, 0.2), + 0px 0px 2px 0px rgba(0, 0, 0, 0.14), + 0px 1px 5px 0px rgba(0, 0, 0, 0.12); } .cursor-move { diff --git a/src/components/Sing/ToolBar/EditTargetSwicher.vue b/src/components/Sing/ToolBar/EditTargetSwicher.vue index a93a4569ce..995de539e6 100644 --- a/src/components/Sing/ToolBar/EditTargetSwicher.vue +++ b/src/components/Sing/ToolBar/EditTargetSwicher.vue @@ -1,30 +1,29 @@ - - diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue index ae085d0095..580c908896 100644 --- a/src/components/Sing/ToolBar/ToolBar.vue +++ b/src/components/Sing/ToolBar/ToolBar.vue @@ -26,18 +26,14 @@ - - - + /> +
{ }); }; -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 }); @@ -472,13 +458,13 @@ onUnmounted(() => { .q-input { :deep(.q-field__control::before) { - border-color: rgba(colors.$display-rgb, 0.3); + border-color: var(--md-sys-color-outline); } } .q-select { :deep(.q-field__control::before) { - border-color: rgba(colors.$display-rgb, 0.3); + border-color: var(--md-sys-color-outline); } } @@ -488,7 +474,7 @@ onUnmounted(() => { display: flex; justify-content: space-between; min-height: 64px; - padding: 0 16px; + padding: 0 4px; width: 100%; } @@ -549,6 +535,16 @@ onUnmounted(() => { pointer-events: none; } +.sing-playback-button { + // primaryボタン + background: var(--md-sys-color-secondary-container); + color: var(--md-sys-color-on-surface); + &:before { + box-shadow: none; + } + //border: 1px solid var(--md-ref-palette-neutral-variant-80); +} + .sing-playhead-position { align-items: center; display: flex; @@ -574,8 +570,10 @@ onUnmounted(() => { .sing-undo-button, .sing-redo-button { + height: 40px; + min-width: 40px; &.disabled { - opacity: 0.4 !important; + opacity: 0.87 !important; } } .sing-redo-button { @@ -592,6 +590,6 @@ onUnmounted(() => { } .sing-snap { - min-width: 104px; + min-width: 80px; } diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts index 8a4835b05f..e65fa822f5 100644 --- a/src/helpers/colors.ts +++ b/src/helpers/colors.ts @@ -28,7 +28,7 @@ export function generateTheme( ): Theme { let theme = themeFromSourceColor(argbFromHex(sourceColor)); - // Apply adjustments to palettes + // パレットの調整 const adjustedPalettes: Record = { ...theme.palettes, }; @@ -43,7 +43,7 @@ export function generateTheme( } } - // Process custom colors + // カスタムカラーの処理 const customColorGroups: CustomColorGroup[] = customColors.map((color) => { const lightColor = adjustedPalettes[color.palette].tone(color.lightTone); const darkColor = adjustedPalettes[color.palette].tone(color.darkTone); @@ -71,11 +71,11 @@ export function generateTheme( }; }); - // Generate new schemes based on adjusted palettes + // パレットに基づいて新しいスキームを生成 const lightScheme = Scheme.light(adjustedPalettes.primary.keyColor.toInt()); const darkScheme = Scheme.dark(adjustedPalettes.primary.keyColor.toInt()); - // Update custom colors with scheme colors + // スキームに基づいてカスタムカラーを更新 customColorGroups.forEach((group) => { group.light.onColor = lightScheme.onPrimary; group.light.colorContainer = lightScheme.primaryContainer; @@ -85,7 +85,7 @@ export function generateTheme( group.dark.onColorContainer = darkScheme.onPrimaryContainer; }); - // Create new theme with adjusted palettes, schemes, and custom colors + // 調整されたパレット、スキーム、カスタムカラーを使用して新しいテーマを作成 theme = { ...theme, palettes: adjustedPalettes, @@ -109,18 +109,43 @@ export function themeToCssVariables( // Set sys colors Object.entries(scheme.toJSON()).forEach(([colorName, color]) => { - const kebabCaseName = colorName + const kebabCasePaletteName = colorName .replace(/([a-z])([A-Z])/g, "$1-$2") .toLowerCase(); - vars[`--md-sys-color-${kebabCaseName}`] = hexFromArgb(color); + vars[`--md-sys-color-${kebabCasePaletteName}`] = hexFromArgb(color); + }); + + // 追加のカラーバリアントの生成と設定 + const colorRoles = [ + "primary", + "secondary", + "tertiary", + "neutral", + "neutralVariant", + "error", + ] as const; + colorRoles.forEach((role) => { + const palette = theme.palettes[role]; + + // dim, bright バリアントの生成 + const dimTone = isDark ? 6 : 87; + const brightTone = isDark ? 24 : 98; + vars[`--md-sys-color-${role}-dim`] = hexFromArgb(palette.tone(dimTone)); + vars[`--md-sys-color-${role}-bright`] = hexFromArgb( + palette.tone(brightTone), + ); }); // Set ref palette for (const [paletteName, palette] of Object.entries(theme.palettes)) { const tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100]; for (const tone of tones) { - vars[`--md-ref-palette-${paletteName.toLowerCase()}-${tone}`] = - hexFromArgb(palette.tone(tone)); + const kebabCasePaletteName = paletteName + .replace(/([a-z])([A-Z])/g, "$1-$2") + .toLowerCase(); + vars[`--md-ref-palette-${kebabCasePaletteName}-${tone}`] = hexFromArgb( + palette.tone(tone), + ); } } diff --git a/src/main.ts b/src/main.ts index 01453b9e5e..be083773c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,80 +17,6 @@ import "./styles/_index.scss"; // ため、それを防止するため自前でdataLayerをあらかじめ用意する window.dataLayer = []; -// ブランドカラー -const sourceColor = "#A5D4AD"; -// パレットの調整 -const adjustments = { - neutral: { chroma: -5, tone: 0 }, - neutralVariant: { chroma: -1, tone: 0 }, -}; -// カスタムカラー(仮) -const customColors: CustomColor[] = [ - { - name: "sing-toolbar", - palette: "neutral", - lightTone: 99, - darkTone: 20, - blend: true, - }, - { - name: "sing-ruler", - palette: "neutralVariant", - lightTone: 90, - darkTone: 10, - blend: true, - }, - { - name: "cell-white", - palette: "neutral", - lightTone: 100, - darkTone: 15, - blend: true, - }, - { - name: "cell-black", - palette: "neutral", - lightTone: 96, - darkTone: 12, - blend: true, - }, - { - name: "sing-grid-measure-line", - palette: "neutral", - lightTone: 70, - darkTone: 40, - blend: true, - }, - { - name: "sing-grid-beat-line", - palette: "neutral", - lightTone: 90, - darkTone: 0, - blend: true, - }, - { - name: "sing-piano-key-white", - palette: "neutral", - lightTone: 100, - darkTone: 80, - blend: true, - }, - { - name: "sing-piano-key-black", - palette: "neutral", - lightTone: 50, - darkTone: 30, - blend: true, - }, -]; -const theme = generateTheme(sourceColor, adjustments, customColors); -const cssVariables = themeToCssVariables(theme, false); - -// CSSに適用する -Object.entries(cssVariables).forEach(([key, value]) => { - document.documentElement.style.setProperty(key, value); -}); - createApp(App) .use(store, storeKey) .use( diff --git a/src/store/setting.ts b/src/store/setting.ts index 6200c8fe44..e8a8b5527f 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -1,4 +1,5 @@ import { Dark, setCssVar, colors } from "quasar"; +import { generateTheme, themeToCssVariables } from "../helpers/colors"; import { SettingStoreState, SettingStoreTypes } from "./type"; import { createUILockAction } from "./ui"; import { createPartialStore } from "./vuex"; @@ -275,6 +276,80 @@ export const settingStore = createPartialStore({ window.backend.setNativeTheme(theme.isDark ? "dark" : "light"); + // ブランドカラー + const sourceColor = "#A5D4AD"; + // パレットの調整 + const adjustments = { + neutral: { chroma: -2 }, + neutralVariant: { chroma: -2 }, + }; + // カスタムカラー(仮) + const customColors = [ + { + name: "sing-toolbar", + palette: "neutralVariant", + lightTone: 99, + darkTone: 20, + blend: true, + }, + { + name: "sing-ruler", + palette: "neutralVariant", + lightTone: 90, + darkTone: 10, + blend: true, + }, + { + name: "cell-white", + palette: "neutral", + lightTone: 100, + darkTone: 15, + blend: true, + }, + { + name: "cell-black", + palette: "neutral", + lightTone: 96, + darkTone: 12, + blend: true, + }, + { + name: "sing-grid-measure-line", + palette: "neutral", + lightTone: 70, + darkTone: 30, + blend: true, + }, + { + name: "sing-grid-beat-line", + palette: "neutral", + lightTone: 90, + darkTone: 0, + blend: true, + }, + { + name: "sing-piano-key-white", + palette: "neutral", + lightTone: 100, + darkTone: 80, + blend: true, + }, + { + name: "sing-piano-key-black", + palette: "neutral", + lightTone: 50, + darkTone: 30, + blend: true, + }, + ]; + const md3theme = generateTheme(sourceColor, adjustments, customColors); + const cssVariables = themeToCssVariables(md3theme, theme.isDark); + + // CSSに適用する + Object.entries(cssVariables).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); + commit("SET_THEME_SETTING", { currentTheme: currentTheme, }); From e230816f32f2a776b1fd92ac0ca22276a6ca8459 Mon Sep 17 00:00:00 2001 From: Romot Date: Tue, 2 Jul 2024 03:48:31 +0900 Subject: [PATCH 05/37] =?UTF-8?q?=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ScoreSequencer.vue | 6 +- src/components/Sing/SequencerKeys.vue | 10 +- src/components/Sing/SequencerNote.vue | 39 +++-- src/components/Sing/SequencerPitch.vue | 4 +- src/components/Sing/SequencerRuler.vue | 2 +- .../Sing/ToolBar/EditTargetSwicher.vue | 5 +- src/components/Sing/ToolBar/ToolBar.vue | 10 +- src/helpers/colors.ts | 162 ++++++++++++------ src/store/setting.ts | 39 +++-- 9 files changed, 186 insertions(+), 91 deletions(-) diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index a897728c69..23788df8bd 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -1591,7 +1591,7 @@ const contextMenuData = ref([ .sequencer-corner { grid-row: 1; grid-column: 1; - background: var(--md-custom-color-sing-ruler); + background: var(--md-sys-color-surface-container-high); } .sequencer-ruler { @@ -1623,11 +1623,11 @@ const contextMenuData = ref([ .sequencer-grid-cell { display: block; - stroke: var(--md-sys-color-grid-beat-line); + stroke: var(--md-custom-color-sing-grid-cell-line); } .sequencer-grid-octave-cell { - stroke: var(--md-sys-color-grid-measure-line); + stroke: var(--md-custom-color-sing-grid-measure-line); } .sequencer-grid-octave-line { diff --git a/src/components/Sing/SequencerKeys.vue b/src/components/Sing/SequencerKeys.vue index 75029d6abd..be2fc05aa7 100644 --- a/src/components/Sing/SequencerKeys.vue +++ b/src/components/Sing/SequencerKeys.vue @@ -181,12 +181,12 @@ onUnmounted(() => { .white-key { fill: var(--md-custom-color-sing-piano-key-white); - stroke: var(--md-custom-color-sing-grid-beat-line); + stroke: var(--md-sys-color-outline-variant); } .white-key-being-pressed { - fill: var(--md-sys-color-secondary-container); - stroke: var(--md-sys-color-primary); + fill: var(--md-sys-color-secondary-fixed-dim); + stroke: var(--md-sys-color-secondary-fixed-dim); } .black-key { @@ -194,10 +194,10 @@ onUnmounted(() => { } .black-key-being-pressed { - fill: var(--md-sys-color-secondary-container); + fill: var(--md-sys-color-secondary-fixed-dim); } .pitchname { - fill: var(--md-sys-color-on-surface); + fill: var(--md-sys-color-on-surface-fixed); } diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index ebb5f96ff4..9a7353eddd 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -302,29 +302,30 @@ const onLyricInput = (event: Event) => { &.below-pitch { .note-bar { - background-color: var(--md-sys-color-primary-container); - border-color: var(--md-sys-color-primary); + background-color: var(--md-sys-color-surface-variant); + border-color: var(--md-sys-color-secondary-fixed-dim); } } &.selected { .note-bar { - background-color: var(--md-ref-palette-primary-95); - outline: solid 2px var(--md-sys-color-inverse-primary); + background-color: var(--md-sys-color-primary-fixed-dim); + border-color: var(--md-sys-color-secondary-fixed); + outline: solid 2px var(--md-sys-color-secondady-fixed); } &.below-pitch { .note-bar { - background-color: var(--md-sys-color-primary-container); + background-color: var(--md-sys-color-surface-variant); } } } // TODO:もっといい見た目を考える &.preview-lyric { .note-bar { - background-color: lab(90, -22.953, 14.365); - border-color: lab(75, -22.953, 14.365); - outline: solid 2px lab(80, -22.953, 14.365); + background-color: var(--md-sys-color-secondary-fixed); + border-color: var(--md-sys-color-primary-fixed); + outline: solid 2px var(--md-sys-color-primary-fixed-dim); } .note-lyric { @@ -333,7 +334,9 @@ const onLyricInput = (event: Event) => { &.below-pitch { .note-bar { - background-color: rgba(hsl(130, 100%, 50%), 0.18); + background-color: var(--md-sys-color-secondary-fixed); + border-color: var(--md-sys-color-primary-fixed); + outline: solid 2px var(--md-sys-color-primary-fixed-dim); } } } @@ -341,18 +344,20 @@ const onLyricInput = (event: Event) => { &.overlapping, &.invalid-phrase { .note-bar { - background-color: rgba(colors.$warning-rgb, 0.5); + background-color: var(--md-sys-color-error-container); + border-color: var(--md-sys-color-error); } .note-lyric { + color: var(--md-sys-color-error); opacity: 0.6; } &.selected { .note-bar { - background-color: rgba(colors.$warning-rgb, 0.5); - border-color: colors.$warning; - outline: 2px solid rgba(colors.$warning-rgb, 0.3); + background-color: var(--md-sys-color-error-container); + border-color: var(--md-sys-color-error); + outline: 2px solid var(--md-sys-color-error-container); } } } @@ -377,8 +382,8 @@ const onLyricInput = (event: Event) => { position: absolute; width: calc(100% + 1px); height: 100%; - border: 1px solid var(--md-ref-palette-neutral-variant-90); - background-color: var(--md-ref-palette-primary-90); + border: 1px solid var(--md-sys-color-secondary-fixed-dim); + background-color: var(--md-sys-color-secondary-fixed); border-radius: 4px; } @@ -391,7 +396,7 @@ const onLyricInput = (event: Event) => { &:hover { // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する - background-color: var(--md-sys-color-inverse-primary); + background-color: var(--md-sys-color-secondary-fixed-dim); } } @@ -404,7 +409,7 @@ const onLyricInput = (event: Event) => { &:hover { // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する - background-color: var(--md-sys-color-inverse-primary); + background-color: var(--md-sys-color-secondary-fixed-dim); } } diff --git a/src/components/Sing/SequencerPitch.vue b/src/components/Sing/SequencerPitch.vue index 3d350a5f5b..044e6eebb8 100644 --- a/src/components/Sing/SequencerPitch.vue +++ b/src/components/Sing/SequencerPitch.vue @@ -35,9 +35,9 @@ type PitchLine = { }; const originalPitchLineColor = new Color(171, 201, 176, 255); -const originalPitchLineWidth = 1.2; +const originalPitchLineWidth = 1; const pitchEditLineColor = new Color(146, 214, 154, 255); -const pitchEditLineWidth = 2; +const pitchEditLineWidth = 1.5; const props = defineProps<{ offsetX: number; diff --git a/src/components/Sing/SequencerRuler.vue b/src/components/Sing/SequencerRuler.vue index 55e3d967a7..0392824635 100644 --- a/src/components/Sing/SequencerRuler.vue +++ b/src/components/Sing/SequencerRuler.vue @@ -188,7 +188,7 @@ onUnmounted(() => { @use "@/styles/colors" as colors; .sequencer-ruler { - background: var(--md-custom-color-sing-ruler); + background: var(--md-sys-color-surface-container-high); height: 40px; position: relative; overflow: hidden; diff --git a/src/components/Sing/ToolBar/EditTargetSwicher.vue b/src/components/Sing/ToolBar/EditTargetSwicher.vue index 995de539e6..b692780b91 100644 --- a/src/components/Sing/ToolBar/EditTargetSwicher.vue +++ b/src/components/Sing/ToolBar/EditTargetSwicher.vue @@ -37,11 +37,12 @@ defineProps<{ diff --git a/src/components/Sing/ToolBar/EditTargetSwicher.vue b/src/components/Sing/ToolBar/EditTargetSwicher.vue index 248b7d991f..6340fc05ff 100644 --- a/src/components/Sing/ToolBar/EditTargetSwicher.vue +++ b/src/components/Sing/ToolBar/EditTargetSwicher.vue @@ -3,25 +3,39 @@ - ノート編集 + + + ノート編集 + - ピッチ編集
Ctrl+クリックで消去
+ + ピッチ編集
Ctrl+クリックで消去 +
@@ -38,11 +52,11 @@ defineProps<{ diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue index 68e39b589a..c1dd37963a 100644 --- a/src/components/Sing/ToolBar/ToolBar.vue +++ b/src/components/Sing/ToolBar/ToolBar.vue @@ -9,6 +9,8 @@ label="音域調整" dense hide-bottom-space + standout + unelevated class="key-range-adjustment" @update:model-value="setKeyRangeAdjustmentInputBuffer" @change="setKeyRangeAdjustment" @@ -18,7 +20,9 @@ :model-value="volumeRangeAdjustmentInputBuffer" label="声量調整" dense + standout hide-bottom-space + unelevated class="volume-range-adjustment" @update:model-value="setVolumeRangeAdjustmentInputBuffer" @change="setVolumeRangeAdjustment" @@ -29,42 +33,53 @@ dense hide-bottom-space standout + unelevated + label="BPM" class="sing-tempo" @update:model-value="setBpmInputBuffer" @change="setTempo" /> - -
- -
/
- -
+ +
@@ -108,33 +123,32 @@ flat dense round - icon="undo" - size="16px" class="sing-undo-button" :disable="!canUndo" @click="undo" - /> + > + + + > + + { @use "@/styles/variables" as vars; @use "@/styles/colors" as colors; -.q-input { - :deep(.q-field__control::before) { - border-color: var(--md-sys-color-outline); - } +// フィールドデフォルト +:deep(.q-field__native) { + text-align: center; + font-size: 16px; + font-weight: 500; } -.q-select { - :deep(.q-field__control::before) { - border-color: var(--md-sys-color-outline); +// background +:deep(.q-field--standout.q-field--highlighted .q-field__control) { + background: var(--md-sys-color-surface-variant); + box-shadow: none; + + &:after { + box-shadow: none; } } +:deep(.q-field--standout .q-field__native) { + padding: 0; +} + +// ラベル(ハイライト) +:deep(.q-field--standout.q-field--highlighted .q-field__label) { + color: var(--md-sys-color-on-surface-variant); +} + +// テキスト(ハイライト) +:deep(.q-field--standout.q-field--highlighted .q-field__native) { + color: var(--md-sys-color-on-surface) !important; +} + +// オプションメニュー全体の背景色 +: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) { + background: var(--md-sys-color-surface-container-high); + padding: 0; +} + +:deep(.sing-beats .q-field__control) { + background: transparent; + padding: 0 4px; +} + +:deep(.sing-time-signature.beats .q-field__control) { + padding-left: 8px; +} + +:deep(.sing-time-signature.beat-type .q-field__control) { + padding-right: 8px; +} + .sing-toolbar { - background: var(--md-sys-color-surface-high); + background: var(--md-sys-color-surface-container); align-items: center; display: flex; justify-content: space-between; @@ -506,7 +568,7 @@ onUnmounted(() => { } .sing-tempo { - margin-left: 8px; + margin-left: 16px; margin-right: 4px; width: 72px; } @@ -518,22 +580,24 @@ onUnmounted(() => { left: 0; } +.sing-time-signature-field { + padding: 0; +} + .sing-beats { - align-items: center; display: flex; - margin-left: 8px; - position: relative; + align-items: center; } -.sing-time-signature { - margin: 0; - position: relative; - width: 32px; +.sing-beats-separator { + color: var(--md-sys-color-on-surface-variant); + padding: 0; + opacity: 0.6; } + .sing-beats-separator { - position: relative; - top: 5px; - margin-right: 8px; + font-weight: 500; + color: var(--md-sys-color-on-surface-variant); pointer-events: none; } @@ -582,8 +646,11 @@ onUnmounted(() => { .sing-undo-button, .sing-redo-button { + color: var(--md-sys-color-on-surface-variant); + width: 40px; + height: 40px; &.disabled { - opacity: 0.87 !important; + opacity: 0.38 !important; } } .sing-redo-button { diff --git a/src/store/setting.ts b/src/store/setting.ts index 11b1e681e5..dde5a8fba7 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -299,6 +299,20 @@ export const settingStore = createPartialStore({ darkTone: 15, 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", @@ -323,14 +337,14 @@ export const settingStore = createPartialStore({ { name: "sing-grid-measure-line", palette: "neutral", - lightTone: 85, - darkTone: 0, + lightTone: 80, + darkTone: 40, blend: true, }, { name: "sing-grid-octave-line", palette: "neutral", - lightTone: 85, + lightTone: 80, darkTone: 0, blend: true, }, @@ -338,7 +352,7 @@ export const settingStore = createPartialStore({ name: "sing-piano-key-white", palette: "neutral", lightTone: 99, - darkTone: 80, + darkTone: 70, blend: true, }, { From 5c9250414cc1afc3740d0d7b67b06bc6dc66001c Mon Sep 17 00:00:00 2001 From: Romot Date: Fri, 5 Jul 2024 01:38:49 +0900 Subject: [PATCH 08/37] =?UTF-8?q?=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sing/CharacterMenuButton/MenuButton.vue | 2 +- .../CharacterMenuButton/SelectedCharacter.vue | 29 ++--- src/components/Sing/SequencerNote.vue | 34 ++--- src/components/Sing/ToolBar/ToolBar.vue | 120 ++++++++++++------ 4 files changed, 117 insertions(+), 68 deletions(-) diff --git a/src/components/Sing/CharacterMenuButton/MenuButton.vue b/src/components/Sing/CharacterMenuButton/MenuButton.vue index 47511384c4..be8063ddc5 100644 --- a/src/components/Sing/CharacterMenuButton/MenuButton.vue +++ b/src/components/Sing/CharacterMenuButton/MenuButton.vue @@ -256,7 +256,7 @@ const engineIcons = computed(() => .character-menu { .q-item { - color: colors.$display; + color: var(--md-sys-color-on-surface); } .q-btn-group { > .q-btn:first-child > :deep(.q-btn__content) { diff --git a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue index e3f3ebccd1..89d21b17f8 100644 --- a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue +++ b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue @@ -5,8 +5,8 @@
- +
@@ -84,11 +80,12 @@ const selectedStyleIconPath = computed(() => { .selected-character { border: 1px solid var(--md-sys-color-outline-variant); - border-radius: 4px; + border-radius: 4px 0 0 4px; align-items: center; display: flex; padding: 4px; position: relative; + height: 56px; .character-avatar-icon { display: block; @@ -101,15 +98,17 @@ 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: 14px; - font-weight: bold; - line-height: 1rem; + font-weight: 500; + line-height: 16px; + margin-bottom: 8px; padding-top: 0; &.skeleton { @@ -120,14 +119,14 @@ const selectedStyleIconPath = computed(() => { .character-style { color: var(--md-sys-color-on-surface-variant); - font-size: 12px; + font-size: 10px; font-weight: 500; - line-height: 1rem; + line-height: 1em; } .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/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index 132845a352..9dcfbd21b6 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -310,14 +310,14 @@ const onLyricInput = (event: Event) => { &.selected { // 仮 .note-bar { - background-color: var(--md-custom-color-brand); - border-color: var(--md-sys-color-primary-fixed); - outline: solid 1px var(--md-sys-color-primary-fixed); + background-color: var(--md-ref-palette-primary-90); + border-color: var(--md-ref-palette-primary-60); } &.below-pitch { .note-bar { background-color: var(--md-sys-color-surface-variant); + border-color: var(--md-sys-color-secondary-fixed); } } @@ -350,13 +350,12 @@ const onLyricInput = (event: Event) => { &.overlapping, &.invalid-phrase { .note-bar { - background-color: var(--md-sys-color-error); - border-color: var(--md-sys-color-error-container); + background-color: var(--md-sys-color-error-container); + border-color: var(--md-sys-color-error); } .note-lyric { - color: var(--md-sys-color-on-error); - opacity: 0.6; + color: var(--md-sys-color-on-error-container); } &.selected { @@ -377,7 +376,7 @@ const onLyricInput = (event: Event) => { padding: 0; background: transparent; color: var(--md-ref-palette-neutral-variant-20); - font-size: 1rem; + font-size: 16px; font-weight: 500; white-space: nowrap; pointer-events: none; @@ -420,18 +419,21 @@ const onLyricInput = (event: Event) => { } .note-lyric-input { - position: relative; - bottom: 0; + position: absolute; + top: 0; font-weight: 500; - min-width: fit-content; - outline: 2px solid var(--md-sys-color-inverse-primary); + font-size: 16px; + max-width: 4rem; + width: fit-content; + background-color: var(--md-ref-palette-neutral-100); + color: var(--md-sys-color-on-surface); + outline: 2px solid var(--md-sys-color-primary-fixed-dim); border-radius: 4px; border: 0; - // boxshadow beautiful md3 medium + // large boxshadow beautiful M3 box-shadow: - 0px 2px 4px -2px rgba(0, 0, 0, 0.2), - 0px 0px 2px 0px rgba(0, 0, 0, 0.14), - 0px 1px 5px 0px rgba(0, 0, 0, 0.12); + 0 4px 6px rgba(0, 0, 0, 0.1), + 0 1px 3px rgba(0, 0, 0, 0.08); } .cursor-move { diff --git a/src/components/Sing/ToolBar/ToolBar.vue b/src/components/Sing/ToolBar/ToolBar.vue index c1dd37963a..1d0825cf91 100644 --- a/src/components/Sing/ToolBar/ToolBar.vue +++ b/src/components/Sing/ToolBar/ToolBar.vue @@ -3,30 +3,32 @@
- - +
+ + +
@@ -44,6 +47,8 @@ dense standout class="sing-time-signature-field" + label="拍子" + stack-label > @@ -27,6 +28,7 @@ import { computed, ref, watch } from "vue"; import ToolBar from "./ToolBar/ToolBar.vue"; import ScoreSequencer from "./ScoreSequencer.vue"; +import ColorSchemeEditor from "@/components/Sing/ColorSchemeEditor.vue"; import EngineStartupOverlay from "@/components/EngineStartupOverlay.vue"; import { useStore } from "@/store"; import onetimeWatch from "@/helpers/onetimeWatch"; diff --git a/src/components/Sing/ThemeChanger.vue b/src/components/Sing/ThemeChanger.vue deleted file mode 100644 index d4271a73a3..0000000000 --- a/src/components/Sing/ThemeChanger.vue +++ /dev/null @@ -1,502 +0,0 @@ - - - - - diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts index f27d977220..51a86126c4 100644 --- a/src/helpers/colors.ts +++ b/src/helpers/colors.ts @@ -15,76 +15,14 @@ import { MaterialDynamicColors, } from "@material/material-color-utilities"; -// カラースキーマの種類 -export type SchemeVariant = - | "content" - | "tonalSpot" - | "neutral" - | "vibrant" - | "expressive" - | "fidelity" - | "monochrome" - | "rainbow" - | "fruitSalad"; - -// カラーパレットのキー(M3準拠) -export type PaletteKey = - | "primary" - | "secondary" - | "tertiary" - | "neutral" - | "neutralVariant" - | "error"; - -// カラー調整 -export interface ColorAdjustment { - hue?: number; - chroma?: number; - tone?: number; - hex?: string; -} - -// テーマオプション -export interface ThemeOptions { - sourceColor: string; - variant?: SchemeVariant; - isDark?: boolean; - contrastLevel?: number; - adjustments?: Partial>; -} - -// 定義済みカスタムカラー -export interface CustomDefinedColor { - name: string; - value: string; - blend: boolean; -} - -// パレットから取得するカスタムカラー -export interface CustomPaletteColor { - name: string; - palette: PaletteKey; - lightTone: number; - darkTone: number; - blend: boolean; -} - -// カラーパレットオプション -export interface TonalPaletteOptions { - color: string; - tonalOffset?: number; -} - -export interface ColorSchemeConfig { - name: string; - sourceColor: string; - variant: SchemeVariant; - isDark: boolean; - contrastLevel: number; - adjustments: Partial>; - customPaletteColors: CustomPaletteColor[]; - customDefinedColors: CustomDefinedColor[]; -} +import { + ColorSchemeConfig, + PaletteKey, + ColorAdjustment, + ThemeOptions, + SchemeVariant, + ColorTheme, +} from "@/type/preload"; // カラースキーマのコンストラクタ const SCHEME_CONSTRUCTORS: Record = { @@ -130,7 +68,7 @@ export const adjustPalette = ( const hexHct = Hct.fromInt(argbFromHex(adjustment.hex)); return TonalPalette.fromHueAndChroma(hexHct.hue, hexHct.chroma); } else { - // 色相、彩度、明度を指定されている場合はテーマを調整 + // 色相、彩度、明度を指定されている場合はテーマパレットを調整 const hue = adjustment.hue != undefined ? adjustment.hue : palette.hue; const chroma = adjustment.chroma != undefined ? adjustment.chroma : palette.chroma; diff --git a/src/store/setting.ts b/src/store/setting.ts index c8a9cf6ae1..e5321041d6 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -1,11 +1,8 @@ import { Dark, setCssVar, colors } from "quasar"; -import { - generateColorTheme, - colorThemeToCssVariables, -} from "../helpers/colors"; import { SettingStoreState, SettingStoreTypes } from "./type"; import { createUILockAction } from "./ui"; import { createPartialStore } from "./vuex"; +import { generateColorTheme, colorThemeToCssVariables } from "@/helpers/colors"; import { HotkeySettingType, SavingSetting, @@ -16,6 +13,7 @@ import { EngineId, ConfirmedTips, RootMiscSettingType, + ColorSchemeConfig, } from "@/type/preload"; import { IsEqual } from "@/type/utility"; @@ -40,6 +38,10 @@ export const settingStoreState: SettingStoreState = { currentTheme: "Default", availableThemes: [], }, + colorSchemeSetting: { + colorScheme: undefined, + availableColorSchemes: [], + }, editorFont: "default", showTextLineNumber: false, showAddAudioItemButton: true, @@ -93,6 +95,9 @@ export const settingStore = createPartialStore({ }); } + // TODO: Hydrate + dispatch("INITIALIZE_COLOR_SCHEME"); + dispatch("SET_ACCEPT_RETRIEVE_TELEMETRY", { acceptRetrieveTelemetry: await window.backend.getSetting( "acceptRetrieveTelemetry", @@ -279,35 +284,83 @@ export const settingStore = createPartialStore({ window.backend.setNativeTheme(theme.isDark ? "dark" : "light"); - // NOTE: 以下をどこかで設定ファイル化 - // カラーを変更したい場合 - // 1. ソースカラーを変更する(テーマ全体) - // 2. ソースカラーからtertiaryカラー(アクセント)を変更する - // 3. テーマの調整をする(adjustments) - // 4. パレットから取得するカラーを設定する - // 5. 定義済みカスタムカラーを設定する(パレット外) - // 6. テーマオプションを設定する(tonalSpot, monochrome, vibrant...etc) - // 7. コントラスト比を設定する - - // ソースカラー - // カラースキーマの読み込み - window.backend.getColorSchemeConfigs().then((configs) => { - const defaultSchemeConfig = configs[0]; + commit("SET_THEME_SETTING", { + currentTheme: currentTheme, + }); + }, + }, + + INITIALIZE_COLOR_SCHEME: { + mutation(state, { colorScheme, availableColorSchemes }) { + state.colorSchemeSetting.colorScheme = colorScheme; + state.colorSchemeSetting.availableColorSchemes = availableColorSchemes; + }, + action: createUILockAction(async ({ commit, state }) => { + try { + const availableColorSchemes = + await window.backend.getColorSchemeConfigs(); + const defaultSchemeConfig = availableColorSchemes[0]; + const isDark = state.themeSetting.currentTheme.isDark ?? false; const colorTheme = generateColorTheme({ ...defaultSchemeConfig, - isDark: theme.isDark, + isDark, }); + + // CSS変数に変換 const cssVariables = colorThemeToCssVariables(colorTheme); - // CSSに適用する + + // CSSに適用 Object.entries(cssVariables).forEach(([key, value]) => { document.documentElement.style.setProperty(key, value); }); - }); - commit("SET_THEME_SETTING", { - currentTheme: currentTheme, - }); + commit("INITIALIZE_COLOR_SCHEME", { + colorScheme: defaultSchemeConfig, + availableColorSchemes, + }); + } catch (error) { + console.error("Error initializing color scheme:", error); + await window.backend.showMessageDialog({ + type: "error", + title: "カラースキーム初期化エラー", + message: "カラースキームの初期化中にエラーが発生しました。", + }); + } + }), + }, + + SET_COLOR_SCHEME_SETTING: { + mutation(state, { colorScheme }) { + state.colorSchemeSetting.colorScheme = colorScheme; }, + action: createUILockAction( + async ( + { commit, state }, + { colorScheme }: { colorScheme: ColorSchemeConfig }, + ) => { + try { + const isDark = state.themeSetting.currentTheme.isDark ?? false; + const colorTheme = generateColorTheme({ + ...colorScheme, + isDark, + }); + const cssVariables = colorThemeToCssVariables(colorTheme); + + Object.entries(cssVariables).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); + + commit("SET_COLOR_SCHEME_SETTING", { colorScheme: colorScheme }); + } catch (error) { + console.error("Error setting color scheme:", error); + await window.backend.showMessageDialog({ + type: "error", + title: "カラースキーム設定エラー", + message: "カラースキームの設定中にエラーが発生しました。", + }); + } + }, + ), }, SET_ACCEPT_RETRIEVE_TELEMETRY: { diff --git a/src/store/type.ts b/src/store/type.ts index b94864d7aa..7f47a317cb 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -52,6 +52,7 @@ import { RootMiscSettingType, EditorType, NoteId, + ColorSchemeConfig, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { @@ -1573,6 +1574,24 @@ export type SettingStoreTypes = { action(payload: { currentTheme: string }): void; }; + INITIALIZE_COLOR_SCHEME: { + mutation: { + colorScheme: ColorSchemeConfig; + availableColorSchemes: ColorSchemeConfig[]; + }; + action(payload: { + colorScheme: ColorSchemeConfig; + availableColorSchemes: ColorSchemeConfig[]; + }): void; + }; + + SET_COLOR_SCHEME_SETTING: { + mutation: { + colorScheme: ColorSchemeConfig; + }; + action(payload: { colorScheme: ColorSchemeConfig }): void; + }; + SET_ACCEPT_RETRIEVE_TELEMETRY: { mutation: { acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus }; action(payload: { diff --git a/src/type/preload.ts b/src/type/preload.ts index 56786c61b2..8d4efe627a 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -558,6 +558,78 @@ export type ThemeSetting = { availableThemes: ThemeConf[]; }; +// カラースキーマの種類 +export type SchemeVariant = + | "content" + | "tonalSpot" + | "neutral" + | "vibrant" + | "expressive" + | "fidelity" + | "monochrome" + | "rainbow" + | "fruitSalad"; + +// カラーパレットのキー(M3準拠) +export type PaletteKey = + | "primary" + | "secondary" + | "tertiary" + | "neutral" + | "neutralVariant" + | "error"; + +// カラー調整 +export interface ColorAdjustment { + hue?: number; + chroma?: number; + tone?: number; + hex?: string; +} + +// テーマオプション +export interface ThemeOptions { + sourceColor: string; + variant?: SchemeVariant; + isDark?: boolean; + contrastLevel?: number; + adjustments?: Partial>; +} + +// 定義済みカスタムカラー +export interface CustomDefinedColor { + name: string; + value: string; + blend: boolean; +} + +// パレットから取得するカスタムカラー +export interface CustomPaletteColor { + name: string; + palette: PaletteKey; + lightTone: number; + darkTone: number; + blend: boolean; +} + +// カラーパレットオプション +export interface TonalPaletteOptions { + color: string; + tonalOffset?: number; +} + +// カラースキーマの設定 +export interface ColorSchemeConfig { + name: string; + sourceColor: string; + variant: SchemeVariant; + isDark: boolean; + contrastLevel: number; + adjustments: Partial>; + customPaletteColors: CustomPaletteColor[]; + customDefinedColors: CustomDefinedColor[]; +} + export const experimentalSettingSchema = z.object({ enablePreset: z.boolean().default(false), shouldApplyDefaultPresetOnVoiceChanged: z.boolean().default(false), From 97313ccb357681bc7efc3ab15cc424256db24815 Mon Sep 17 00:00:00 2001 From: Romot Date: Wed, 17 Jul 2024 16:09:16 +0900 Subject: [PATCH 27/37] =?UTF-8?q?=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A:=20?= =?UTF-8?q?=E5=8B=95=E7=9A=84=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/color-schemes/none.json | 124 ++++++++++++ src/components/Sing/ColorSchemeEditor.vue | 180 +++++++++++++----- src/components/Sing/SequencerLyricInput.vue | 2 +- src/components/Sing/SequencerNote.vue | 44 +++-- .../Sing/SequencerPhraseIndicator.vue | 30 +-- src/components/Sing/SequencerPitch.vue | 28 ++- src/components/Sing/SingEditor.vue | 2 +- src/helpers/colors.ts | 117 +++++++----- src/store/setting.ts | 32 ++-- src/store/type.ts | 5 +- src/type/preload.ts | 11 ++ 11 files changed, 428 insertions(+), 147 deletions(-) create mode 100644 public/color-schemes/none.json diff --git a/public/color-schemes/none.json b/public/color-schemes/none.json new file mode 100644 index 0000000000..9a3d9e2bf9 --- /dev/null +++ b/public/color-schemes/none.json @@ -0,0 +1,124 @@ +{ + "label": "調整なし", + "sourceColor": "#A5D4AD", + "variant": "tonalSpot", + "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/src/components/Sing/ColorSchemeEditor.vue b/src/components/Sing/ColorSchemeEditor.vue index b16ea3f5a1..e8906451ec 100644 --- a/src/components/Sing/ColorSchemeEditor.vue +++ b/src/components/Sing/ColorSchemeEditor.vue @@ -1,22 +1,56 @@ diff --git a/src/components/Sing/SequencerLyricInput.vue b/src/components/Sing/SequencerLyricInput.vue index 8fcba51e28..126cf0206d 100644 --- a/src/components/Sing/SequencerLyricInput.vue +++ b/src/components/Sing/SequencerLyricInput.vue @@ -123,7 +123,7 @@ watch( // 入力欄が背景に邪魔されないようぼかす(あとかっこいい) backdrop-filter: blur(1px) grayscale(70%); color: var(--md-sys-color-on-primary-fixed); - outline: 2px solid var(--md-sys-color-primary); + outline: 1px solid var(--md-sys-color-primary); border-radius: 4px; border: 0; box-shadow: diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index 1ac68e03ec..c12bf3e7d6 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -314,24 +314,6 @@ const onLeftEdgeMouseDown = (event: MouseEvent) => { } } - &.below-pitch { - .note-bar { - background-color: var(--md-sys-color-surface-variant); - border-color: var(--md-sys-color-outline-variant); - opacity: 0.38; - } - - .note-lyric { - color: var(--md-sys-color-on-surface); - opacity: 0.72; - } - - .note-right-edge:hover, - .note-left-edge:hover { - background-color: transparent; - } - } - &.preview-lyric { .note-bar { background-color: var(--md-sys-color-primary-fixed); @@ -368,6 +350,32 @@ const onLeftEdgeMouseDown = (event: MouseEvent) => { } } } + + &.below-pitch { + .note-bar { + background-color: var(--md-sys-color-surface-variant); + border-color: var(--md-sys-color-outline-variant); + opacity: 0.38; + } + + .note-lyric { + color: var(--md-sys-color-on-surface); + opacity: 0.72; + } + + .note-right-edge:hover, + .note-left-edge:hover { + background-color: transparent; + } + + &.overlapping, + &.invalid-phrase { + .note-bar { + background-color: var(--md-sys-color-error-container); + border-color: var(--md-sys-color-error); + } + } + } } .cursor-move { diff --git a/src/components/Sing/SequencerPhraseIndicator.vue b/src/components/Sing/SequencerPhraseIndicator.vue index a9e40ee822..bfd9db49fa 100644 --- a/src/components/Sing/SequencerPhraseIndicator.vue +++ b/src/components/Sing/SequencerPhraseIndicator.vue @@ -13,6 +13,8 @@ const props = defineProps<{ }>(); const store = useStore(); +const colorScheme = store.state.colorSchemeSetting.colorScheme; +console.log(colorScheme.systemColors.primaryFixedDim); const classNames: Record = { WAITING_TO_BE_RENDERED: "waiting-to-be-rendered", NOW_RENDERING: "now-rendering", @@ -31,28 +33,28 @@ const className = computed(() => { @use "@/styles/colors" as colors; .waiting-to-be-rendered { - background-color: colors.$background; + background-color: var(--md-sys-color-background); background-image: linear-gradient( to right, - rgba(colors.$primary-rgb, 0.8), - rgba(colors.$primary-rgb, 0.8) + rgba(var(--md-sys-color-secondary-fixed-rgb), 0.8), + rgba(var(--md-sys-color-secondary-fixed-rgb), 0.8) ); } .now-rendering { - border: 1px solid rgba(colors.$primary-rgb, 0.7); - background-color: colors.$background; + border: 1px solid rgba(var(--md-sys-color-primary-fixed-dim-rgb), 0.7); + background-color: var(--md-sys-color-background); background-size: 28px 28px; background-image: linear-gradient( -45deg, - colors.$primary, - colors.$primary 25%, - rgba(colors.$primary-rgb, 0.36) 25%, - rgba(colors.$primary-rgb, 0.36) 50%, - colors.$primary 50%, - colors.$primary 75%, - rgba(colors.$primary-rgb, 0.36) 75%, - rgba(colors.$primary-rgb, 0.36) 100% + var(--md-sys-color-secondary-fixed), + var(--md-sys-color-secondary-fixed) 25%, + rgba(var(--md-sys-color-secondary-fixed-rgb), 0.36) 25%, + rgba(var(--md-sys-color-secondary-fixed-rgb), 0.36) 50%, + var(--md-sys-color-secondary-fixed) 50%, + var(--md-sys-color-secondary-fixed) 75%, + rgba(var(--md-sys-color-secondary-fixed-rgb), 0.36) 75%, + rgba(var(--md-sys-color-secondary-fixed-rgb), 0.36) 100% ); animation: stripes-animation 0.7s linear infinite; } @@ -67,7 +69,7 @@ const className = computed(() => { } .could-not-render { - background-color: colors.$warning; + background-color: var(--md-ref-palette-error-60); } .playable { diff --git a/src/components/Sing/SequencerPitch.vue b/src/components/Sing/SequencerPitch.vue index 3f941b5775..c149657e90 100644 --- a/src/components/Sing/SequencerPitch.vue +++ b/src/components/Sing/SequencerPitch.vue @@ -6,6 +6,7 @@ import { ref, watch, computed } from "vue"; import * as PIXI from "pixi.js"; import AsyncLock from "async-lock"; +import { arrayFromRgba } from "@/helpers/colors"; import { useStore } from "@/store"; import { UNVOICED_PHONEMES, @@ -49,6 +50,7 @@ const { warn, error } = createLogger("SequencerPitch"); const store = useStore(); const tpqn = computed(() => store.state.tpqn); const tempos = computed(() => [store.state.tempos[0]]); +const colorScheme = computed(() => store.state.colorSchemeSetting.colorScheme); const singingGuides = computed(() => [...store.state.singingGuides.values()]); const pitchEditData = computed(() => { return store.getters.SELECTED_TRACK.pitchEditData; @@ -56,15 +58,33 @@ const pitchEditData = computed(() => { const previewPitchEdit = computed(() => props.previewPitchEdit); const editFrameRate = computed(() => store.state.editFrameRate); -// NOTE: カラースキーマオブジェクトから変換できるようにする +// TODO: このままだとテーマ変更で更新されないため、変更に追随する必要あり +const pitchLineColor = [ + ...arrayFromRgba(colorScheme.value.systemColors.outline).splice(0, 3), + 255, +]; +const pitchEditLineColor = [ + ...arrayFromRgba(colorScheme.value.systemColors.primaryFixedDim).splice(0, 3), + 255, +]; const originalPitchLine: PitchLine = { - color: new Color(160, 160, 160, 255 * 0.8), - width: 1, + color: new Color( + pitchLineColor[0], + pitchLineColor[1], + pitchLineColor[2], + pitchLineColor[3], + ), + width: 1.5, pitchDataMap: new Map(), lineStripMap: new Map(), }; const pitchEditLine: PitchLine = { - color: new Color(152, 213, 164, 255), + color: new Color( + pitchEditLineColor[0], + pitchEditLineColor[1], + pitchEditLineColor[2], + pitchEditLineColor[3], + ), width: 2, pitchDataMap: new Map(), lineStripMap: new Map(), diff --git a/src/components/Sing/SingEditor.vue b/src/components/Sing/SingEditor.vue index 19eaaf0a78..3b165f028d 100644 --- a/src/components/Sing/SingEditor.vue +++ b/src/components/Sing/SingEditor.vue @@ -20,7 +20,7 @@
- +
diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts index 51a86126c4..f2e927c27e 100644 --- a/src/helpers/colors.ts +++ b/src/helpers/colors.ts @@ -21,7 +21,7 @@ import { ColorAdjustment, ThemeOptions, SchemeVariant, - ColorTheme, + ColorScheme, } from "@/type/preload"; // カラースキーマのコンストラクタ @@ -58,26 +58,39 @@ export const rgbaFromArgb = (argb: number, alpha: number = 1): string => { 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 + ? [Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4])] + : []; +}; + // テーマパレットの調整 export const adjustPalette = ( palette: TonalPalette, adjustment: ColorAdjustment, ): TonalPalette => { + let hct: Hct; + if (adjustment.hex) { - // HEX指定されている場合はHEX値からトーナルパレットを作成 - const hexHct = Hct.fromInt(argbFromHex(adjustment.hex)); - return TonalPalette.fromHueAndChroma(hexHct.hue, hexHct.chroma); + // HEX指定されている場合はHEX値からHCTを作成 + hct = Hct.fromInt(argbFromHex(adjustment.hex)); } else { - // 色相、彩度、明度を指定されている場合はテーマパレットを調整 - const hue = adjustment.hue != undefined ? adjustment.hue : palette.hue; - const chroma = - adjustment.chroma != undefined ? adjustment.chroma : palette.chroma; - const tone = adjustment.tone != undefined ? adjustment.tone : 50; - return TonalPalette.fromHueAndChroma( - Hct.from(hue, chroma, tone).hue, - Hct.from(hue, chroma, tone).chroma, - ); + // HEX指定がない場合は既存のパレットの値を使用 + hct = Hct.from(palette.hue, palette.chroma, 50); // トーンは50をデフォルトとする } + + // hue, chroma, toneが指定されている場合は上書き + const hue = adjustment.hue != undefined ? adjustment.hue : hct.hue; + const chroma = + adjustment.chroma != undefined ? adjustment.chroma : hct.chroma; + const tone = adjustment.tone != undefined ? adjustment.tone : hct.tone; + + // 調整された値でHCTオブジェクトを作成 + const adjustedHct = Hct.from(hue, chroma, tone); + + // 調整されたHCTオブジェクトからトーナルパレットを作成 + return TonalPalette.fromHueAndChroma(adjustedHct.hue, adjustedHct.chroma); }; // M3準拠のダイナミックスキーマの生成 @@ -121,25 +134,16 @@ export const createDynamicScheme = (options: ThemeOptions): DynamicScheme => { return scheme; }; -// テーマの色スキーマ -export interface ColorTheme { - scheme: DynamicScheme; - systemColors: Record; - paletteTones: Record>; - customPaletteColors: Record; - customDefinedColors: Record; -} - // テーマの色スキーマの生成 -export const generateColorTheme = ( - schemeConfig: ColorSchemeConfig, -): ColorTheme => { +export const generateColorScheme = ( + colorSchemeConfig: ColorSchemeConfig, +): ColorScheme => { const scheme = createDynamicScheme({ - sourceColor: schemeConfig.sourceColor, - variant: schemeConfig.variant, - isDark: schemeConfig.isDark, - contrastLevel: schemeConfig.contrastLevel, - adjustments: schemeConfig.adjustments, + sourceColor: colorSchemeConfig.sourceColor, + variant: colorSchemeConfig.variant, + isDark: colorSchemeConfig.isDark, + contrastLevel: colorSchemeConfig.contrastLevel, + adjustments: colorSchemeConfig.adjustments, }); // システムカラーの生成 @@ -172,8 +176,21 @@ export const generateColorTheme = ( ); // カスタムパレットの色の生成 - const customPaletteColors = schemeConfig.customPaletteColors.reduce( - (acc, { name, palette, lightTone, darkTone }) => { + const customPaletteColors = colorSchemeConfig.customPaletteColors.reduce( + ( + acc: { [x: string]: string }, + { + name, + palette, + lightTone, + darkTone, + }: { + name: string; + palette: PaletteKey; + lightTone: number; + darkTone: number; + }, + ) => { acc[name] = rgbaFromArgb( ( scheme[`${palette}Palette` as keyof DynamicScheme] as TonalPalette @@ -185,8 +202,11 @@ export const generateColorTheme = ( ); // カスタム定義色の生成 - const customDefinedColors = schemeConfig.customDefinedColors.reduce( - (acc, { name, value }) => { + const customDefinedColors = colorSchemeConfig.customDefinedColors.reduce( + ( + acc: Record, + { name, value }: { name: string; value: string }, + ) => { acc[name] = value; return acc; }, @@ -199,32 +219,43 @@ export const generateColorTheme = ( paletteTones, customPaletteColors, customDefinedColors, + colorSchemeConfig, }; }; // テーマの色スキーマをCSS変数に変換 -export const colorThemeToCssVariables = ( - theme: ColorTheme, +export const colorSchemeToCssVariables = ( + theme: ColorScheme, ): Record => { const cssVars: Record = {}; + 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(theme.systemColors).forEach(([name, color]) => { - cssVars[`--md-sys-color-${name.replace(/([A-Z])/g, "-$1").toLowerCase()}`] = - color; + const cssName = name.replace(/([A-Z])/g, "-$1").toLowerCase(); + setColorVar("--md-sys-color-", cssName, color as string); }); - Object.entries(theme.paletteTones).forEach(([key, tones]) => { + Object.entries( + theme.paletteTones as Record>, + ).forEach(([key, tones]) => { Object.entries(tones).forEach(([tone, color]) => { - cssVars[`--md-ref-palette-${key}-${tone}`] = color; + setColorVar(`--md-ref-palette-${key}-`, tone, color as string); }); }); Object.entries(theme.customPaletteColors).forEach(([name, color]) => { - cssVars[`--md-custom-color-${name}`] = color; + setColorVar("--md-custom-color-", name, color as string); }); Object.entries(theme.customDefinedColors).forEach(([name, color]) => { - cssVars[`--md-custom-color-${name}`] = color; + setColorVar("--md-custom-color-", name, color as string); }); return cssVars; @@ -233,7 +264,7 @@ export const colorThemeToCssVariables = ( // テーマに合わせて色を調整するユーティリティ関数 ex: トラックカラーをテーマにあわせる export const adjustColorToTheme = ( color: string, - theme: ColorTheme, + theme: ColorScheme, targetKey: PaletteKey = "primary", ): string => { const sourceHct = Hct.fromInt(argbFromHex(color)); diff --git a/src/store/setting.ts b/src/store/setting.ts index e5321041d6..00220a572b 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -2,7 +2,10 @@ import { Dark, setCssVar, colors } from "quasar"; import { SettingStoreState, SettingStoreTypes } from "./type"; import { createUILockAction } from "./ui"; import { createPartialStore } from "./vuex"; -import { generateColorTheme, colorThemeToCssVariables } from "@/helpers/colors"; +import { + generateColorScheme, + colorSchemeToCssVariables, +} from "@/helpers/colors"; import { HotkeySettingType, SavingSetting, @@ -300,22 +303,22 @@ export const settingStore = createPartialStore({ const availableColorSchemes = await window.backend.getColorSchemeConfigs(); const defaultSchemeConfig = availableColorSchemes[0]; - const isDark = state.themeSetting.currentTheme.isDark ?? false; - const colorTheme = generateColorTheme({ + const isDark = state.themeSetting.currentTheme === "Dark" ?? false; + const colorScheme = generateColorScheme({ ...defaultSchemeConfig, isDark, }); // CSS変数に変換 - const cssVariables = colorThemeToCssVariables(colorTheme); + const cssVariables = colorSchemeToCssVariables(colorScheme); // CSSに適用 Object.entries(cssVariables).forEach(([key, value]) => { - document.documentElement.style.setProperty(key, value); + document.documentElement.style.setProperty(key, value as string); }); commit("INITIALIZE_COLOR_SCHEME", { - colorScheme: defaultSchemeConfig, + colorScheme: colorScheme, availableColorSchemes, }); } catch (error) { @@ -328,7 +331,6 @@ export const settingStore = createPartialStore({ } }), }, - SET_COLOR_SCHEME_SETTING: { mutation(state, { colorScheme }) { state.colorSchemeSetting.colorScheme = colorScheme; @@ -336,21 +338,23 @@ export const settingStore = createPartialStore({ action: createUILockAction( async ( { commit, state }, - { colorScheme }: { colorScheme: ColorSchemeConfig }, + { colorSchemeConfig }: { colorSchemeConfig: ColorSchemeConfig }, ) => { try { - const isDark = state.themeSetting.currentTheme.isDark ?? false; - const colorTheme = generateColorTheme({ - ...colorScheme, + const isDark = state.themeSetting.currentTheme === "Dark" ?? false; + const colorScheme = generateColorScheme({ + ...colorSchemeConfig, isDark, }); - const cssVariables = colorThemeToCssVariables(colorTheme); + const cssVariables = colorSchemeToCssVariables(colorScheme); Object.entries(cssVariables).forEach(([key, value]) => { - document.documentElement.style.setProperty(key, value); + document.documentElement.style.setProperty(key, value as string); }); - commit("SET_COLOR_SCHEME_SETTING", { colorScheme: colorScheme }); + commit("SET_COLOR_SCHEME_SETTING", { + colorScheme: colorScheme, + }); } catch (error) { console.error("Error setting color scheme:", error); await window.backend.showMessageDialog({ diff --git a/src/store/type.ts b/src/store/type.ts index 7f47a317cb..ec5be62a1f 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -53,6 +53,7 @@ import { EditorType, NoteId, ColorSchemeConfig, + ColorScheme, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { @@ -1587,9 +1588,9 @@ export type SettingStoreTypes = { SET_COLOR_SCHEME_SETTING: { mutation: { - colorScheme: ColorSchemeConfig; + colorScheme: ColorScheme; }; - action(payload: { colorScheme: ColorSchemeConfig }): void; + action(payload: { colorSchemeConfig: ColorSchemeConfig }): void; }; SET_ACCEPT_RETRIEVE_TELEMETRY: { diff --git a/src/type/preload.ts b/src/type/preload.ts index 8d4efe627a..a5b2291b25 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"; @@ -630,6 +631,16 @@ export interface ColorSchemeConfig { customDefinedColors: CustomDefinedColor[]; } +// カラースキーマ +export interface ColorScheme { + scheme: DynamicScheme; + systemColors: Record; + paletteTones: Record>; + customPaletteColors: Record; + customDefinedColors: Record; + colorSchemeConfig: ColorSchemeConfig; +} + export const experimentalSettingSchema = z.object({ enablePreset: z.boolean().default(false), shouldApplyDefaultPresetOnVoiceChanged: z.boolean().default(false), From 84d5fb7631cf1fc576cddf3e5866bc62a0f2d693 Mon Sep 17 00:00:00 2001 From: Romot Date: Wed, 17 Jul 2024 19:36:50 +0900 Subject: [PATCH 28/37] =?UTF-8?q?=E4=BB=95=E6=8E=9B=E3=81=8B=E3=82=8A:=20?= =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=BC=E3=82=B9=E3=82=AD=E3=83=BC=E3=83=A0?= =?UTF-8?q?=E3=82=A8=E3=83=87=E3=82=A3=E3=82=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/color-schemes/monochrome.json | 122 +++++++++++ public/color-schemes/none.json | 4 +- public/color-schemes/test.json | 134 +++++++++++++ .../Dialog/SettingDialog/SettingDialog.vue | 10 + .../Sing/CharacterMenuButton/MenuButton.vue | 2 +- .../CharacterMenuButton/SelectedCharacter.vue | 5 + src/components/Sing/ColorSchemeEditor.vue | 189 ++++++++++++++---- .../Sing/SequencerPhraseIndicator.vue | 2 - src/components/Sing/SingEditor.vue | 10 +- src/helpers/colors.ts | 69 ++++--- src/store/setting.ts | 13 +- src/type/preload.ts | 1 + 12 files changed, 485 insertions(+), 76 deletions(-) create mode 100644 public/color-schemes/monochrome.json create mode 100644 public/color-schemes/test.json diff --git a/public/color-schemes/monochrome.json b/public/color-schemes/monochrome.json new file mode 100644 index 0000000000..a6a8b18557 --- /dev/null +++ b/public/color-schemes/monochrome.json @@ -0,0 +1,122 @@ +{ + "label": "モノクローム", + "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 index 9a3d9e2bf9..664eac3ca5 100644 --- a/public/color-schemes/none.json +++ b/public/color-schemes/none.json @@ -1,7 +1,7 @@ { - "label": "調整なし", + "label": "調整なし(M3デフォルト)", "sourceColor": "#A5D4AD", - "variant": "tonalSpot", + "variant": "content", "contrastLevel": 0.0, "adjustments": {}, "customPaletteColors": [ diff --git a/public/color-schemes/test.json b/public/color-schemes/test.json new file mode 100644 index 0000000000..e7446228f2 --- /dev/null +++ b/public/color-schemes/test.json @@ -0,0 +1,134 @@ +{ + "label": "テスト", + "sourceColor": "#9cf476", + "variant": "content", + "contrastLevel": 0, + "adjustments": { + "neutral": { + "chroma": 3.7 + }, + "neutralVariant": { + "chroma": 24.4, + "tone": 0 + }, + "tertiary": { + "hex": "#ff1a6a", + "chroma": 19.4, + "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": "secondary", + "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": "secondary", + "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": "tertiary", + "lightTone": 83, + "darkTone": 15, + "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": "neutral", + "lightTone": 100, + "darkTone": 20, + "blend": true + } + ], + "customDefinedColors": [] +} \ No newline at end of file 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 91223e9164..8e0e6927b3 100644 --- a/src/components/Sing/CharacterMenuButton/MenuButton.vue +++ b/src/components/Sing/CharacterMenuButton/MenuButton.vue @@ -256,7 +256,7 @@ const engineIcons = useEngineIcons(() => store.state.engineManifests); 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 206a10f036..3eb407d7c3 100644 --- a/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue +++ b/src/components/Sing/CharacterMenuButton/SelectedCharacter.vue @@ -87,6 +87,11 @@ const selectedStyleIconPath = computed(() => { 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); } diff --git a/src/components/Sing/ColorSchemeEditor.vue b/src/components/Sing/ColorSchemeEditor.vue index e8906451ec..287719b261 100644 --- a/src/components/Sing/ColorSchemeEditor.vue +++ b/src/components/Sing/ColorSchemeEditor.vue @@ -1,25 +1,30 @@