bottom.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  2. <ui-editor-toolbar toolbar class="animation-toolbar">
  3. <div class="top-bar">
  4. <div class="play-bar">
  5. <ui-icon
  6. :type="play ? 'a-pause' : 'a-play'"
  7. ctrl
  8. @click="
  9. () => {
  10. play = !play;
  11. $emit('update:follow', play);
  12. }
  13. "
  14. />
  15. </div>
  16. <div class="scale-bar">
  17. <ui-icon
  18. type="reduce"
  19. class="icon"
  20. ctrl
  21. @click="scale = Math.max(0.5, scale - 0.5)"
  22. />
  23. <Slider
  24. class="slider"
  25. v-model:value="scale"
  26. :min="0.5"
  27. :max="8"
  28. :step="0.01"
  29. :tooltipOpen="false"
  30. />
  31. <ui-icon
  32. type="magnify"
  33. class="icon"
  34. ctrl
  35. @click="scale = Math.min(8, scale + 0.5)"
  36. />
  37. </div>
  38. </div>
  39. <div class="oper-bar" :class="{ disabled: play }">
  40. <Renderer v-model:scale="scale" ref="renderer">
  41. <v-group>
  42. <template v-if="am">
  43. <template v-for="prop in tlProps">
  44. <TimeLine
  45. v-if="am[prop.attr].length"
  46. :items="am[prop.attr]"
  47. :height="prop.height"
  48. :background="prop.background"
  49. :opacity="prop.opacity"
  50. :top="prop.top"
  51. :itemsRenderer="prop.component"
  52. @update="({ ndx, time }) => (am![prop.attr][ndx].time = time)"
  53. @add="
  54. (item) => {
  55. am![prop.attr].push(item);
  56. $emit('update:active', {
  57. key: prop.attr,
  58. ndx: am![prop.attr].length - 1,
  59. });
  60. }
  61. "
  62. @del="(ndx) => am![prop.attr].splice(ndx, 1)"
  63. :active="
  64. prop.attr === active?.key ? am[prop.attr][active.ndx] : undefined
  65. "
  66. @update:active="(active: any) => $emit('update:active', active && { key: prop.attr, ndx: am![prop.attr].indexOf(active) })"
  67. />
  68. </template>
  69. <empty v-if="!count" />
  70. </template>
  71. </v-group>
  72. <v-group>
  73. <Time @update-current-time="(time) => $emit('update:currentTime', time)">
  74. <TimeCurrent
  75. :currentTime="currentTime"
  76. @update:current-time="(time) => $emit('update:currentTime', time)"
  77. :follow="follow"
  78. />
  79. </Time>
  80. </v-group>
  81. </Renderer>
  82. </div>
  83. </ui-editor-toolbar>
  84. </template>
  85. <script lang="ts" setup>
  86. import { Slider } from "ant-design-vue";
  87. import { computed, ref, watch, watchEffect } from "vue";
  88. import { AnimationModel } from "@/store/animation";
  89. import Renderer from "@/components/drawing/renderer.vue";
  90. import Time from "@/components/drawing-time/time.vue";
  91. import TimeCurrent from "@/components/drawing-time/current.vue";
  92. import TimeLine from "@/components/drawing-time-line/index.vue";
  93. import TimeLineFrame from "@/components/drawing-time-line/frame.vue";
  94. import TimeLineAction from "@/components/drawing-time-line/action.vue";
  95. import empty from "@/components/drawing-time-line/empty.vue";
  96. import { Active } from "./type";
  97. const props = defineProps<{
  98. am?: AnimationModel;
  99. active?: Active;
  100. currentTime: number;
  101. follow: boolean;
  102. }>();
  103. const emit = defineEmits<{
  104. (e: "update:active", data: Active | undefined): void;
  105. (e: "update:follow", data: boolean): void;
  106. (e: "update:currentTime", v: number): void;
  107. }>();
  108. const scale = ref(1);
  109. const tlProps = [
  110. {
  111. attr: "frames",
  112. component: TimeLineFrame,
  113. height: 30,
  114. top: 24,
  115. background: "#000",
  116. opacity: 0.5,
  117. },
  118. {
  119. attr: "actions",
  120. component: TimeLineAction,
  121. height: 30,
  122. top: 65,
  123. background: "#000",
  124. opacity: 0.5,
  125. },
  126. {
  127. attr: "subtitles",
  128. component: TimeLineAction,
  129. height: 30,
  130. top: 105,
  131. background: "#000",
  132. opacity: 0.5,
  133. },
  134. {
  135. attr: "paths",
  136. component: TimeLineAction,
  137. height: 30,
  138. top: 145,
  139. background: "#000",
  140. opacity: 0.5,
  141. },
  142. ] as const;
  143. const count = computed(() =>
  144. Object.values(tlProps).reduce((t, c) => (props.am ? t + props.am[c.attr].length : 0), 0)
  145. );
  146. const play = ref(false);
  147. watch(play, (_a, _b, onCleanup) => {
  148. let isDes = false;
  149. let prevNow = Date.now();
  150. const animation = () => {
  151. if (play.value && !isDes) {
  152. const curNow = Date.now();
  153. emit("update:currentTime", props.currentTime + (curNow - prevNow) * 0.001);
  154. prevNow = curNow;
  155. requestAnimationFrame(animation);
  156. }
  157. };
  158. animation();
  159. onCleanup(() => (isDes = true));
  160. });
  161. const renderer = ref<any>();
  162. watch(
  163. () => props.am,
  164. () => renderer.value.updateSize()
  165. );
  166. </script>
  167. <style scoped lang="scss">
  168. .top-bar {
  169. display: flex;
  170. height: 40px;
  171. align-items: center;
  172. border-bottom: 1px solid rgba(255, 255, 255, 0.16);
  173. .play-bar {
  174. flex: 1;
  175. display: flex;
  176. justify-content: center;
  177. .icon {
  178. font-size: 16px;
  179. }
  180. }
  181. .scale-bar {
  182. width: 180px;
  183. flex: 0 0 auto;
  184. display: flex;
  185. align-items: center;
  186. padding: 0 20px;
  187. position: relative;
  188. &::before {
  189. content: "";
  190. position: absolute;
  191. left: 0;
  192. width: 1px;
  193. height: 16px;
  194. background: rgba(255, 255, 255, 0.16);
  195. }
  196. .slider {
  197. flex: 1;
  198. }
  199. .icon {
  200. margin: 0 5px;
  201. font-size: 16px;
  202. }
  203. }
  204. }
  205. .oper-bar {
  206. height: calc(100% - 40px);
  207. }
  208. </style>