|
3 | 3 | class="taskbar-lyric" |
4 | 4 | :class="{ dark: state.isDark, 'layout-reverse': !state.isCenter, floating: isFloating }" |
5 | 5 | :style="rootStyle" |
| 6 | + @mouseenter="handleMouseEnter" |
| 7 | + @mouseleave="handleMouseLeave" |
6 | 8 | > |
7 | 9 | <div class="cover-wrapper" v-if="coverSrc && settingStore.taskbarLyricShowCover"> |
8 | 10 | <Transition name="cross-fade"> |
9 | 11 | <img :key="coverSrc" :src="coverSrc" class="cover" alt="cover" @error="onCoverError" /> |
10 | 12 | </Transition> |
11 | 13 | </div> |
12 | 14 |
|
| 15 | + <Transition name="controls-expand"> |
| 16 | + <div class="media-controls" v-if="showControls"> |
| 17 | + <div class="control-btn" @click.stop="controlAction('playPrev')"> |
| 18 | + <SvgIcon name="SkipPrev" /> |
| 19 | + </div> |
| 20 | + <div class="control-btn" @click.stop="controlAction('playOrPause')"> |
| 21 | + <SvgIcon :name="state.isPlaying ? 'Pause' : 'Play'" /> |
| 22 | + </div> |
| 23 | + <div class="control-btn" @click.stop="controlAction('playNext')"> |
| 24 | + <SvgIcon name="SkipNext" /> |
| 25 | + </div> |
| 26 | + </div> |
| 27 | + </Transition> |
| 28 | + |
13 | 29 | <div class="content" :style="contentStyle"> |
14 | 30 | <Transition :name="settingStore.taskbarLyricAnimationMode" mode="out-in"> |
15 | 31 | <TransitionGroup tag="div" class="lyric-list-wrapper" name="lyric-list" :key="transitionKey"> |
16 | | - <div |
| 32 | + <div |
17 | 33 | v-for="item in displayItems" |
18 | 34 | :key="item.key" |
19 | 35 | class="lyric-item" |
@@ -57,6 +73,8 @@ import LyricScroll from "./LyricScroll.vue"; |
57 | 73 | const settingStore = useSettingStore(); |
58 | 74 | const route = useRoute(); |
59 | 75 | const isFloating = computed(() => route.query.mode === "floating"); |
| 76 | +const isHovering = ref(false); |
| 77 | +const showControls = computed(() => !isFloating.value && isHovering.value); |
60 | 78 |
|
61 | 79 | /** |
62 | 80 | * 只有当 IPC 时间与本地时间误差超过 250ms 时,才同步 IPC 的时间 |
@@ -138,6 +156,25 @@ const lyricFontFamily = computed(() => { |
138 | 156 | return font === "default" ? "inherit" : font; |
139 | 157 | }); |
140 | 158 |
|
| 159 | +const handleMouseEnter = () => { |
| 160 | + if (!isFloating.value) isHovering.value = true; |
| 161 | +}; |
| 162 | +
|
| 163 | +const handleMouseLeave = () => { |
| 164 | + isHovering.value = false; |
| 165 | +}; |
| 166 | +
|
| 167 | +const controlAction = (action: "playPrev" | "playOrPause" | "playNext") => { |
| 168 | + const ipc = window.electron?.ipcRenderer; |
| 169 | + if (!ipc) return; |
| 170 | +
|
| 171 | + if (action === "playOrPause") { |
| 172 | + state.isPlaying = !state.isPlaying; |
| 173 | + } |
| 174 | +
|
| 175 | + ipc.send("send-to-main-win", action); |
| 176 | +}; |
| 177 | +
|
141 | 178 | const transitionKey = computed(() => { |
142 | 179 | if (!currentLyricText.value) { |
143 | 180 | return `meta-${state.title}-${state.artist}`; |
@@ -597,6 +634,11 @@ $radius: 4px; |
597 | 634 | font-size: clamp(12px, 29vh, 26px); |
598 | 635 | -webkit-app-region: drag; |
599 | 636 |
|
| 637 | + .media-controls, |
| 638 | + .control-btn { |
| 639 | + -webkit-app-region: no-drag; |
| 640 | + } |
| 641 | +
|
600 | 642 | &:hover, |
601 | 643 | &:active { |
602 | 644 | background-color: transparent; |
@@ -666,7 +708,7 @@ $radius: 4px; |
666 | 708 | ); |
667 | 709 | --mask-horizontal: linear-gradient( |
668 | 710 | to right, |
669 | | - transparent 0, |
| 711 | + transparent 0%, |
670 | 712 | black var(--mask-gap), |
671 | 713 | black calc(100% - var(--mask-gap)), |
672 | 714 | transparent 100% |
@@ -825,6 +867,66 @@ $radius: 4px; |
825 | 867 | transform: translate3d(-5px, 0, 0); |
826 | 868 | } |
827 | 869 | } |
| 870 | +
|
| 871 | +.media-controls { |
| 872 | + display: flex; |
| 873 | + align-items: center; |
| 874 | + justify-content: center; |
| 875 | + height: 100%; |
| 876 | + max-width: 8.6em; |
| 877 | + gap: 0.4em; |
| 878 | + overflow: hidden; |
| 879 | + z-index: 10; |
| 880 | +
|
| 881 | + .control-btn { |
| 882 | + display: flex; |
| 883 | + align-items: center; |
| 884 | + justify-content: center; |
| 885 | + width: 4.4em; |
| 886 | + height: 2.3em; |
| 887 | + font-size: 1.3em; |
| 888 | + color: inherit; |
| 889 | + border-radius: $radius; |
| 890 | + border: 1px solid rgba(128, 128, 128, 0.4); |
| 891 | + box-sizing: border-box; |
| 892 | + transition: |
| 893 | + background-color 0.2s, |
| 894 | + transform 0.1s, |
| 895 | + border-color 0.2s; |
| 896 | +
|
| 897 | + &:hover { |
| 898 | + background-color: rgba(128, 128, 128, 0.2); |
| 899 | + border-color: rgba(128, 128, 128, 0.7); |
| 900 | + opacity: 1; |
| 901 | + } |
| 902 | +
|
| 903 | + &:active { |
| 904 | + transform: scale(0.92); |
| 905 | + background-color: rgba(128, 128, 128, 0.3); |
| 906 | + border-color: rgba(128, 128, 128, 0.9); |
| 907 | + } |
| 908 | + } |
| 909 | +} |
| 910 | +
|
| 911 | +.controls-expand { |
| 912 | + &-enter-active, |
| 913 | + &-leave-active { |
| 914 | + transition: all 0.4s var(--lyric-ease); |
| 915 | + } |
| 916 | +
|
| 917 | + &-enter-from, |
| 918 | + &-leave-to { |
| 919 | + max-width: 0; |
| 920 | + opacity: 0; |
| 921 | + margin: 0; |
| 922 | + } |
| 923 | +
|
| 924 | + &-enter-to, |
| 925 | + &-leave-from { |
| 926 | + max-width: 8.6em; |
| 927 | + opacity: 1; |
| 928 | + } |
| 929 | +} |
828 | 930 | </style> |
829 | 931 |
|
830 | 932 | <style lang="scss"> |
|
0 commit comments