diff --git a/css/panels.css b/css/panels.css
index 468df0b59..ddc73ca00 100644
--- a/css/panels.css
+++ b/css/panels.css
@@ -1936,13 +1936,24 @@
opacity: 0.25;
}
#timeline_graph_editor .keyframe {
- height: 16px;
- width: 11px;
+ height: 16px;
+ width: 16px;
+ justify-content: center;
+ display: flex;
+ align-items: center;
+ }
+ #timeline_graph_editor .keyframe:hover {
+ height: 20px;
+ width: 20px;
+ translate: -2px -3px;
+ color: var(--hover-color) !important;
}
#timeline_graph_editor .keyframe > i {
font-size: 12px;
- margin-top: 2px;
- margin-left: -2px;
+ margin: 0;
+ }
+ #timeline_graph_editor .keyframe:hover > i {
+ font-size: 16px;
}
#timeline_graph_editor_amplifier {
diff --git a/css/setup.css b/css/setup.css
index fc44a1118..9285cc062 100644
--- a/css/setup.css
+++ b/css/setup.css
@@ -184,146 +184,182 @@
}
- .icon-keyframe_discontinuous_bezier:before {
- content: "\e92d";
- }
- .icon-keyframe_smooth:before {
- content: "\e92c";
- }
- .icon-keyframe_step:before {
- content: "\e92b";
- }
- .icon-keyframe_bezier:before {
- content: "\e92a";
- }
.icon-collada:before {
- content: "\e926";
+ content: "\e926";
}
.icon-fbx:before {
- content: "\e927";
+ content: "\e927";
}
.icon-format_bedrock_block:before {
- content: "\e928";
+ content: "\e928";
}
.icon-format_bedrock_entity:before {
- content: "\e929";
- }
- .icon-keyframe:before {
- content: "\e924";
- }
- .icon-keyframe_discontinuous:before {
- content: "\e925";
+ content: "\e929";
}
.icon-gizmo:before {
- content: "\e922";
+ content: "\e922";
}
.icon-gltf:before {
- content: "\e923";
+ content: "\e923";
}
.icon-format_bedrock_legacy:before {
- content: "\e91a";
+ content: "\e91a";
}
.icon-crossbow:before {
- content: "\e91b";
+ content: "\e91b";
}
.icon-format_bedrock:before {
- content: "\e91c";
+ content: "\e91c";
}
.icon-format_block:before {
- content: "\e91d";
+ content: "\e91d";
}
.icon-format_free:before {
- content: "\e91e";
+ content: "\e91e";
}
.icon-format_hytale:before {
- content: "\e91f";
+ content: "\e91f";
}
.icon-format_java:before {
- content: "\e920";
+ content: "\e920";
}
.icon-format_optifine:before {
- content: "\e921";
+ content: "\e921";
}
.icon-sketchfab:before {
- content: "\e919";
+ content: "\e919";
}
.icon-blockbench_file:before {
- content: "\e900";
+ content: "\e900";
}
.icon-vertexsnap:before {
- content: "\e901";
+ content: "\e901";
}
.icon-create_bitmap:before {
- content: "\e902";
+ content: "\e907";
}
.icon-objects:before {
- content: "\e903";
+ content: "\e908";
}
.icon-bow:before {
- content: "\e904";
+ content: "\e90b";
}
.icon-bb_interface:before {
- content: "\e905";
+ content: "\e90d";
}
.icon-blockbench:before {
- content: "\e906";
+ content: "\e911";
}
.icon-x11:before {
- content: "\e907";
+ content: "\e912";
}
.icon-baby_zombie:before {
- content: "\e908";
+ content: "\e913";
}
.icon-armor_stand:before {
- content: "\e909";
+ content: "\e914";
}
.icon-armor_stand_small:before {
- content: "\e90a";
+ content: "\e915";
}
.icon-ground:before {
- content: "\e90b";
+ content: "\e916";
}
.icon-hud:before {
- content: "\e90c";
+ content: "\e917";
}
.icon-inventory_full:before {
- content: "\e90d";
+ content: "\e918";
}
.icon-inventory_nine:before {
- content: "\e90e";
+ content: "\e924";
}
.icon-inventory_single:before {
- content: "\e90f";
+ content: "\e925";
}
.icon-player_head:before {
- content: "\e910";
+ content: "\e92a";
}
.icon-zombie:before {
- content: "\e911";
+ content: "\e92b";
}
.icon-blockbench_inverted:before {
- content: "\e912";
+ content: "\e92c";
}
.icon-optifine_file:before {
- content: "\e913";
+ content: "\e92d";
}
.icon-saved:before {
- content: "\e914";
+ content: "\e92e";
}
.icon-player:before {
- content: "\e915";
+ content: "\e92f";
}
.icon-mirror_x:before {
- content: "\e916";
+ content: "\e930";
}
.icon-mirror_y:before {
- content: "\e917";
+ content: "\e931";
}
.icon-mirror_z:before {
- content: "\e918";
+ content: "\e932";
+ }
+
+
+ .icon-keyframe_linear:before {
+ content: "\e933";
+ }
+ .icon-keyframe_bezier:before {
+ content: "\e934";
+ }
+ .icon-keyframe_smooth:before {
+ content: "\e935";
+ }
+ .icon-keyframe_step:before {
+ content: "\e936";
+ }
+ .icon-keyframe_linear_discontinuous:before {
+ content: "\e937";
+ }
+ .icon-keyframe_bezier_discontinuous:before {
+ content: "\e938";
+ }
+ .icon-keyframe_step_discontinuous:before {
+ content: "\e90e";
+ }
+ .icon-keyframe_linear_molang:before {
+ content: "\e90a";
+ }
+ .icon-keyframe_smooth_molang:before {
+ content: "\e90c";
+ }
+ .icon-keyframe_bezier_molang:before {
+ content: "\e903";
+ }
+ .icon-keyframe_step_molang:before {
+ content: "\e910";
+ }
+ .icon-keyframe_linear_discontinuous_molang:before {
+ content: "\e909";
+ }
+ .icon-keyframe_bezier_discontinuous_molang:before {
+ content: "\e902";
+ }
+ .icon-keyframe_step_discontinuous_molang:before {
+ content: "\e90f";
+ }
+ .icon-keyframe_hidden:before {
+ content: "\e904";
+ }
+ .icon-keyframe_hidden_discontinuous:before {
+ content: "\e905";
+ }
+ .icon-keyframe_hidden_molang:before {
+ content: "\e906";
}
+
.icon {
user-select: none;
@@ -412,6 +448,25 @@
--color-spline-handle-aligned: #F0B000;
--color-spline-handle-free: #00D123;
+ --color-keyframe: var(--color-text);
+ --color-keyframe-selected: var(--color-accent);
+ --color-keyframe-hovered: var(--color-light);
+ --color-keyframe-collapsed: #495061;
+ --color-keyframe-bezier-handle-dot: var(--color-back);
+ --color-keyframe-bezier-handle-line: var(--color-grid);
+
+ /* values for keyframe and timeline styling are used in js, so the pixel suffix is not use */
+ --timeline-channel-height: 24;
+ --keyframe-radius: 16;
+ --keyframe-radius-hover: 20;
+ --keyframe-radius-hidden: 12;
+
+ --keyframe-graph-radius: 12;
+ --keyframe-graph-radius-hover: 16;
+ --keyframe-bezier-handle-diameter: 10;
+ --keyframe-bezier-stroke-width: 3;
+ /* end of timeline & keyframe values */
+
--font-custom-main: '';
--font-custom-headline: '';
--font-custom-code: '';
diff --git a/font/icomoon.ttf b/font/icomoon.ttf
index 24c276197..460a7dc9b 100644
Binary files a/font/icomoon.ttf and b/font/icomoon.ttf differ
diff --git a/font/icomoon.woff b/font/icomoon.woff
index 4e5cf1cb7..9897269cf 100644
Binary files a/font/icomoon.woff and b/font/icomoon.woff differ
diff --git a/icons/icomoon/keyframes/blank/keyframe_bezier_blank.svg b/icons/icomoon/keyframes/blank/keyframe_bezier_blank.svg
new file mode 100644
index 000000000..ae4fc3b4c
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_bezier_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_bezier_discontinuous_blank.svg b/icons/icomoon/keyframes/blank/keyframe_bezier_discontinuous_blank.svg
new file mode 100644
index 000000000..3f5f249a5
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_bezier_discontinuous_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_bezier_discontinuous_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_bezier_discontinuous_molang_blank.svg
new file mode 100644
index 000000000..455e8b247
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_bezier_discontinuous_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_bezier_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_bezier_molang_blank.svg
new file mode 100644
index 000000000..399baba39
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_bezier_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_hidden_blank.svg b/icons/icomoon/keyframes/blank/keyframe_hidden_blank.svg
new file mode 100644
index 000000000..133ee8e42
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_hidden_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_hidden_discontinuous_blank.svg b/icons/icomoon/keyframes/blank/keyframe_hidden_discontinuous_blank.svg
new file mode 100644
index 000000000..18764e543
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_hidden_discontinuous_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_hidden_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_hidden_molang_blank.svg
new file mode 100644
index 000000000..382338671
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_hidden_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_linear_blank.svg b/icons/icomoon/keyframes/blank/keyframe_linear_blank.svg
new file mode 100644
index 000000000..a2b4a6c4f
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_linear_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_linear_discontinuous_blank.svg b/icons/icomoon/keyframes/blank/keyframe_linear_discontinuous_blank.svg
new file mode 100644
index 000000000..897a895f8
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_linear_discontinuous_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_linear_discontinuous_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_linear_discontinuous_molang_blank.svg
new file mode 100644
index 000000000..c9628cb1e
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_linear_discontinuous_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_linear_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_linear_molang_blank.svg
new file mode 100644
index 000000000..77c667989
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_linear_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_smooth_blank.svg b/icons/icomoon/keyframes/blank/keyframe_smooth_blank.svg
new file mode 100644
index 000000000..0af781690
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_smooth_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_smooth_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_smooth_molang_blank.svg
new file mode 100644
index 000000000..7a1103fd0
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_smooth_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_step_blank.svg b/icons/icomoon/keyframes/blank/keyframe_step_blank.svg
new file mode 100644
index 000000000..1ca95fd6c
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_step_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_step_discontinuous_blank.svg b/icons/icomoon/keyframes/blank/keyframe_step_discontinuous_blank.svg
new file mode 100644
index 000000000..9d225ee2b
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_step_discontinuous_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_step_discontinuous_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_step_discontinuous_molang_blank.svg
new file mode 100644
index 000000000..a73176b34
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_step_discontinuous_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframes/blank/keyframe_step_molang_blank.svg b/icons/icomoon/keyframes/blank/keyframe_step_molang_blank.svg
new file mode 100644
index 000000000..32772d59d
--- /dev/null
+++ b/icons/icomoon/keyframes/blank/keyframe_step_molang_blank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/icomoon/keyframe.svg b/icons/icomoon/keyframes/keyframe.svg
similarity index 100%
rename from icons/icomoon/keyframe.svg
rename to icons/icomoon/keyframes/keyframe.svg
diff --git a/icons/icomoon/keyframe_bezier.svg b/icons/icomoon/keyframes/keyframe_bezier.svg
similarity index 100%
rename from icons/icomoon/keyframe_bezier.svg
rename to icons/icomoon/keyframes/keyframe_bezier.svg
diff --git a/icons/icomoon/keyframe_discontinuous.svg b/icons/icomoon/keyframes/keyframe_discontinuous.svg
similarity index 100%
rename from icons/icomoon/keyframe_discontinuous.svg
rename to icons/icomoon/keyframes/keyframe_discontinuous.svg
diff --git a/icons/icomoon/keyframe_discontinuous_bezier.svg b/icons/icomoon/keyframes/keyframe_discontinuous_bezier.svg
similarity index 100%
rename from icons/icomoon/keyframe_discontinuous_bezier.svg
rename to icons/icomoon/keyframes/keyframe_discontinuous_bezier.svg
diff --git a/icons/icomoon/keyframes/keyframe_icons.af b/icons/icomoon/keyframes/keyframe_icons.af
new file mode 100644
index 000000000..e74736e96
Binary files /dev/null and b/icons/icomoon/keyframes/keyframe_icons.af differ
diff --git a/icons/icomoon/keyframe_smooth.svg b/icons/icomoon/keyframes/keyframe_smooth.svg
similarity index 100%
rename from icons/icomoon/keyframe_smooth.svg
rename to icons/icomoon/keyframes/keyframe_smooth.svg
diff --git a/icons/icomoon/keyframe_step.svg b/icons/icomoon/keyframes/keyframe_step.svg
similarity index 100%
rename from icons/icomoon/keyframe_step.svg
rename to icons/icomoon/keyframes/keyframe_step.svg
diff --git a/js/animations/Timeline.vue b/js/animations/Timeline.vue
new file mode 100644
index 000000000..3d567923e
--- /dev/null
+++ b/js/animations/Timeline.vue
@@ -0,0 +1,1724 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/animations/keyframe.js b/js/animations/keyframe.js
index d1e2aa882..c42f68d41 100644
--- a/js/animations/keyframe.js
+++ b/js/animations/keyframe.js
@@ -474,6 +474,7 @@ export class Keyframe {
if (this.transform) Timeline.vue.graph_editor_channel = this.channel;
+ Timeline.vue.refreshTimelineCanvas();
return this;
}
clickSelect(event) {
@@ -681,6 +682,9 @@ export function updateKeyframeSelection() {
}
BARS.updateConditions()
Blockbench.dispatchEvent('update_keyframe_selection');
+
+ // If the selector isn't the one calling this, framerate drops dramatically.
+ if (!Timeline.selector.selecting) Timeline.vue.refreshTimelineCanvas();
}
export function selectAllKeyframes() {
if (!Animation.selected) return;
@@ -878,6 +882,8 @@ BARS.defineActions(function() {
Animator.preview()
BarItems.slider_keyframe_time.update()
Undo.finishEdit('Move keyframes back')
+
+ Timeline.vue.refreshTimelineCanvas();
}
})
new Action('move_keyframe_forth', {
@@ -894,6 +900,8 @@ BARS.defineActions(function() {
Animator.preview()
BarItems.slider_keyframe_time.update()
Undo.finishEdit('Move keyframes forwards')
+
+ Timeline.vue.refreshTimelineCanvas();
}
})
function slideKeyframes(difference, event) {
diff --git a/js/animations/timeline.js b/js/animations/timeline.js
index 0862cfab6..1ca7ab646 100644
--- a/js/animations/timeline.js
+++ b/js/animations/timeline.js
@@ -1,5 +1,6 @@
import { markerColors } from "../marker_colors";
import { dragHelper } from "../util/drag_helper";
+import TimelineComponent from "./Timeline.vue"
export class TimelineMarker {
constructor(data) {
@@ -94,7 +95,8 @@ export const Timeline = {
!e.target.classList.contains('keyframe_section') &&
!e.target.classList.contains('animator_head_bar') &&
e.target.id !== 'timeline_graph_editor' &&
- e.target.id !== 'timeline_body_inner'
+ e.target.id !== 'timeline_body_keyframe_canvas' &&
+ Timeline.vue.keyframeHoverUuid !== ""
)) {
return
};
@@ -207,6 +209,7 @@ export const Timeline = {
}
updateKeyframeSelection()
+ Timeline.vue.refreshTimelineCanvas();
},
end(e) {
e.stopPropagation();
@@ -232,6 +235,8 @@ export const Timeline = {
.hide();
Undo.finishSelection('Select keyframes');
}
+
+ Timeline.vue.refreshTimelineCanvas();
},
},
setTime(seconds, editing) {
@@ -581,6 +586,7 @@ export const Timeline = {
body.scrollTop += event.deltaY/4;
}
Timeline.updateSize()
+ Timeline.vue.refreshTimelineCanvas();
event.preventDefault();
});
@@ -623,6 +629,7 @@ export const Timeline = {
Timeline.selected.empty();
Timeline.vue.markers = [];
Timeline.vue.animation_length = 0;
+ Timeline.vue.clearTimelineCanvas();
updateKeyframeSelection();
},
start() {
@@ -787,19 +794,6 @@ StateMemory.init("timeline_channels", "object", {
});
Interface.definePanels(() => {
- function eventTargetToAnimator(target) {
- let target_node = target;
- let i = 0;
- while (target_node && target_node.classList && !target_node.classList.contains('animator')) {
- if (i < 3 && target_node) {
- target_node = target_node.parentNode;
- i++;
- } else {
- return [];
- }
- }
- return [Timeline.animators.find(animator => animator.uuid == target_node.attributes.uuid.value), target_node];
- }
Timeline.panel = new Panel('timeline', {
icon: 'timeline',
condition: {modes: ['animate'], method: () => !AnimationController.selected},
@@ -836,1074 +830,7 @@ Interface.definePanels(() => {
this.inside_vue.timeline_height = this.inside_vue.$el.clientHeight;
}
},
- component: {
- name: 'panel-timeline',
- data() {return {
- size: 300,
- length: 10,
- timeline_height: 100,
- animation_length: 0,
- scroll_left: 0,
- scroll_top: 0,
- head_width: Blockbench.isMobile ? 108 : Interface.data.timeline_head,
- timecodes: [],
- animators: Timeline.animators,
- markers: [],
- custom_range: Timeline.custom_range,
- waveforms: Timeline.waveforms,
- focus_channel: null,
- playhead: Timeline.time,
- timestamp: '0',
- framenumber: '0',
-
- graph_editor_open: false,
- graph_editor_channel: 'rotation',
- graph_editor_axis: 'x',
- graph_offset: 200,
- graph_size: 200,
- show_other_graphs: true,
- include_other_graphs: true,
- show_zero_line: true,
- show_all_handles: !Settings.get('only_selected_bezier_handles'),
- loop_graphs: [''],
-
- onion_skin_selectable: BarItems.animation_onion_skin.value,
- onion_skin_time: 0,
-
- channels: StateMemory.timeline_channels
- }},
- computed: {
- graph_editor_animator() {
- return this.animators.find(animator => animator.selected && animator instanceof BoneAnimator);
- },
- zero_line() {
- let height = this.graph_offset;
- return `M0 ${height} L10000 ${height}`
- },
- one_line() {
- let height = this.graph_offset - this.graph_size;
- return `M0 ${height} L10000 ${height}`
- },
- rulers() {
- let lines = [];
- let values = [0];
-
- let interval_options = [1, 2, 4, 8, 10, 20, 25, 50, 100, 200, 250, 400, 500];
- let estimate = 100 / Timeline.vue.graph_size;
- if (estimate > 500) return values;
- let interval = Math.snapToValues(estimate, interval_options);
- let box_height = Timeline.vue.$refs.timeline_body.clientHeight;
-
- for (let i = 1; i < 20; i += 1) {
- let value = i * interval;
- let keep_going = false;
- if (this.graph_offset - value * this.graph_size > 0) {
- values.push(value);
- keep_going = true;
- }
- if (this.graph_offset + value * this.graph_size < box_height) {
- values.push(i * -interval);
- keep_going = true;
- }
- if (!keep_going) break;
- }
-
- values.forEach(value => {
- let height = this.graph_offset - this.graph_size * value;
- lines.push({
- position: height,
- label: Math.round(value),
- path: `M0 ${height} L10000 ${height}`
- });
- })
- return lines;
- },
- graphs() {
- let ba = this.graph_editor_animator;
- if (!ba || !ba[this.graph_editor_channel] || !ba[this.graph_editor_channel].length) {
- this.loop_graphs.empty();
- return [];
- }
- let original_time = Timeline.time;
- let step = 2;
- let clientWidth = this.$refs.timeline_body ? this.$refs.timeline_body.clientWidth : 400;
- let clientHeight = this.$refs.timeline_body ? this.$refs.timeline_body.clientHeight : 400;
- let keyframes = ba[this.graph_editor_channel];
- let points = [];
- let loop_points = [];
-
- let min = this.show_zero_line ? -1 : Timeline.graph_editor_limit,
- max = this.show_zero_line ? 1 : -Timeline.graph_editor_limit;
-
- for (let time = Math.clamp(this.scroll_left - 9, 0, Infinity); time < (clientWidth + this.scroll_left - this.head_width); time += step) {
- Timeline.time = time / this.size;
-
- let snap_kf = keyframes.find(kf => Timeline.time <= kf.time && Timeline.time > kf.time - step / this.size );
- if (snap_kf) {
- Timeline.time = snap_kf.time;
- }
- Animator.resetLastValues();
- let values = [
- (this.show_other_graphs || this.graph_editor_axis === 'x') ? ba.interpolate(this.graph_editor_channel, false, 'x') : 0,
- (this.show_other_graphs || this.graph_editor_axis === 'y') ? ba.interpolate(this.graph_editor_channel, false, 'y') : 0,
- (this.show_other_graphs || this.graph_editor_axis === 'z') ? ba.interpolate(this.graph_editor_channel, false, 'z') : 0
- ];
- let value = values[this.graph_editor_axis_number];
- if (snap_kf) snap_kf.display_value = value;
-
- if (Timeline.time > Animation.selected.length && Animation.selected.length && Animation.selected.loop === 'loop') {
- if (points.length && !loop_points.length) loop_points.push(points.last())
- loop_points.push(values);
- } else {
- points.push(values);
- if (this.show_other_graphs && this.include_other_graphs) {
- min = Math.min(min, ...values);
- max = Math.max(max, ...values);
- } else {
- min = Math.min(min, value);
- max = Math.max(max, value);
- }
- }
- }
- keyframes.forEach(kf => {
- if (kf.interpolation === 'bezier') {
- min = Math.min(min, kf.display_value + kf.bezier_left_value[this.graph_editor_axis_number]);
- max = Math.max(max, kf.display_value + kf.bezier_left_value[this.graph_editor_axis_number]);
- min = Math.min(min, kf.display_value + kf.bezier_right_value[this.graph_editor_axis_number]);
- max = Math.max(max, kf.display_value + kf.bezier_right_value[this.graph_editor_axis_number]);
- }
- })
-
- Timeline.time = original_time;
-
- let padding = 16;
- let min_size = 2.4;
- let unit_size = Math.clamp(max-min, min_size, Timeline.graph_editor_limit);
- this.graph_size = (clientHeight - 2*padding) / unit_size;
- let blend = Math.clamp(1 - (max-min) / min_size, 0, 1)
- this.graph_offset = clientHeight - padding + (this.graph_size * (min - unit_size/2 * blend ) );
-
- let graphs = this.show_other_graphs ? ['', '', ''] : [''];
- points.forEach((values, i) => {
- let command = i == 0 ? 'M' : 'L';
- if (this.show_other_graphs) {
- values.forEach((value, axis) => {
- graphs[axis] += `${command}${i*step} ${this.graph_offset - value * this.graph_size} `;
- })
- } else {
- graphs[0] += `${command}${i*step} ${this.graph_offset - values[this.graph_editor_axis_number] * this.graph_size} `;
- }
- })
-
- this.loop_graphs.empty();
- if (loop_points.length) {
- if (this.show_other_graphs) {
- this.loop_graphs.push('', '', '');
- } else {
- this.loop_graphs.push('');
- }
- loop_points.forEach((values, i) => {
- let command = i == 0 ? 'M' : 'L';
- i = i + points.length - 1;
- if (this.show_other_graphs) {
- values.forEach((value, axis) => {
- this.loop_graphs[axis] += `${command}${i*step} ${this.graph_offset - value * this.graph_size} `;
- })
- } else {
- this.loop_graphs[0] += `${command}${i*step} ${this.graph_offset - values[this.graph_editor_axis_number] * this.graph_size} `;
- }
- })
- }
-
- return graphs;
- },
- graph_editor_axis_number() {
- return getAxisNumber(this.graph_editor_axis)
- }
- },
- methods: {
- tl,
- updateTimecodes() {
- if (!this._isMounted) return;
- this.timecodes.empty();
- let timeline_container_width = Panels.timeline.node.clientWidth - this.head_width;
- this.length = Timeline.getMaxLength();
-
- var step = 1
- if (this.size < 1) {step = 1}
- else if (this.size < 20) {step = 4}
- else if (this.size < 40) {step = 2}
- else if (this.size < 100) {step = 1}
- else if (this.size < 256) {step = 0.5}
- else if (this.size < 520) {step = 0.25}
- else if (this.size < 660) {step = 0.2}
- else if (this.size < 860) {step = 0.1}
- else {step = 0.05}
-
- // Rounding to "FPS" to better match snapping
- if (step < 1) {
- let substep_simplification = Math.max((Math.floor(Math.sqrt(step / Timeline.getStep()))-1), 1);
- var FPS = Timeline.getStep() / substep_simplification;
- step = Math.round(step/FPS) * FPS
- }
-
- // Substep simplification
- let substeps = step / Timeline.getStep()
- while (substeps > 8) {
- substeps /= 2;
- }
-
- // Generate
- var i = Math.floor(this.scroll_left / this.size / step) * step;
- while (i < Math.ceil((this.scroll_left + timeline_container_width) / this.size / step) * step) {
- if (settings.timecode_frame_number.value) {
- var text = Math.round(i / Timeline.getStep());
- } else {
- var text = Math.round(i*100)/100;
- }
- this.timecodes.push({
- time: i,
- width: step,
- substeps,
- text,
- })
- i += step;
- }
-
- if (this.graph_editor_open) {
- this.graph_size += 1e-5;
- }
- },
- updateGraph() {
- if (this.graph_editor_open) {
- this.graph_size += 1e-7;
- }
- },
- toggleAnimator(animator) {
- animator.expanded = !animator.expanded;
- },
- removeAnimator(animator) {
- Timeline.animators.remove(animator);
- },
- toggleGlobalSpace(animator) {
- Undo.initEdit({animations: [Animation.selected]});
- animator.rotation_global = !animator.rotation_global;
- Undo.finishEdit('Toggle rotation in global space');
- Animator.preview();
- },
- selectChannel(animator, channel) {
- if (this.graph_editor_channel == channel && animator.selected) return;
- if (!animator.channels[channel].transform) return;
- Undo.initSelection();
- if (!animator.selected) animator.select();
- // Select keyframe in new channel
- if (animator[channel].length && Keyframe.selected.length > 0) {
- if (animator[channel].length == 1 && Math.epsilon(animator[channel][0].time, Timeline.time, 0.002)) {
- animator[channel][0].select();
- } else if (animator[channel].find(kf => Math.epsilon(kf.time, Keyframe.selected[0].time))) {
- let kf = animator[channel].find(kf => Math.epsilon(kf.time, Keyframe.selected[0].time, 0.002));
- kf.select();
- } else {
- let kf = animator[channel].slice().sort((a, b) => Math.abs(a.time - Timeline.time) - Math.abs(b.time - Timeline.time))[0];
- kf.select();
- }
- }
- this.graph_editor_channel = channel;
- Undo.finishSelection('Select animation channel');
- },
- getColor(index) {
- if (index == -1 || index == undefined) return;
- return markerColors[index % markerColors.length].standard;
- },
- getWaveformPoints(samples, size) {
- let height = 23;
- let points = [`0,${height}`];
- samples.forEach((sample, i) => {
- points.push(`${(i + 0.5) / Timeline.waveform_sample_rate * size},${(1 - sample) * height}`);
- })
- points.push(`${(samples.length) / Timeline.waveform_sample_rate * size},${height}`)
- return points.join(' ');
- },
- updateScroll() {
- this.scroll_left = this.$refs.timeline_body ? this.$refs.timeline_body.scrollLeft : 0;
- this.scroll_top = this.$refs.timeline_body ? this.$refs.timeline_body.scrollTop : 0;
- },
- openContextMenu(event) {
- if (event.target.nodeName == 'KEYFRAME' || event.target.parentElement.nodeName == 'KEYFRAME') return;
- if (Blockbench.hasFlag('no_context_menu')) return;
- event.preventDefault();
- Timeline.menu.open(event, event);
- },
- dragAnimator(animator, e1) {
- if (getFocusedTextInput()) return;
- if (e1.button == 1 || e1.button == 2) return;
- convertTouchEvent(e1);
-
- let active = false;
- let helper;
- let timeout;
- let drop_target, drop_target_node, order;
- let last_event = e1;
-
- function move(e2) {
- convertTouchEvent(e2);
- let offset = [
- e2.clientX - e1.clientX,
- e2.clientY - e1.clientY,
- ]
- if (!active) {
- let distance = Math.sqrt(Math.pow(offset[0], 2) + Math.pow(offset[1], 2))
- if (Blockbench.isTouch) {
- if (distance > 20 && timeout) {
- clearTimeout(timeout);
- timeout = null;
- } else {
- document.getElementById('timeline_body_inner').scrollTop += last_event.clientY - e2.clientY;
- }
- } else if (distance > 6) {
- active = true;
- Undo.initSelection();
- }
- } else {
- if (e2) e2.preventDefault();
-
- if (Menu.open) Menu.open.hide();
-
- if (!helper) {
- helper = document.createElement('div');
- helper.id = 'animation_drag_helper';
- let icon = document.createElement('i'); icon.className = 'material-icons'; icon.innerText = 'chevron_right'; helper.append(icon);
- let span = document.createElement('span'); span.innerText = animator.name; helper.append(span);
- document.body.append(helper);
- }
- helper.style.left = `${e2.clientX}px`;
- helper.style.top = `${e2.clientY}px`;
-
- // drag
- $('.drag_hover').removeClass('drag_hover');
- $('.animator[order]').attr('order', null);
-
- let target = document.elementFromPoint(e2.clientX, e2.clientY);
- [drop_target, drop_target_node] = eventTargetToAnimator(target);
- if (drop_target) {
- let location = e2.clientY - $(drop_target_node).offset().top;
- let half_height = drop_target_node.clientHeight/2;
- let order = location <= half_height ? -1 : 1;
- drop_target_node.setAttribute('order', order)
- drop_target_node.classList.add('drag_hover');
- }
- }
- last_event = e2;
- }
- function off(e2) {
- if (helper) helper.remove();
- removeEventListeners(document, 'mousemove touchmove', move);
- removeEventListeners(document, 'mouseup touchend', off);
- $('.drag_hover').removeClass('drag_hover');
- $('.animator[order]').attr('order', null);
- if (Blockbench.isTouch) clearTimeout(timeout);
-
- if (active && !open_menu) {
- convertTouchEvent(e2);
- let target = document.elementFromPoint(e2.clientX, e2.clientY);
- let [target_animator] = eventTargetToAnimator(target);
- if (!target_animator || target_animator == animator ) return;
-
- let index = Timeline.animators.indexOf(target_animator);
- if (index == -1) return;
- if (order == 1) index++;
- if (Timeline.animators[index] == animator) return;
- Timeline.animators.remove(animator);
- Timeline.animators.splice(index, 0, animator);
- Undo.finishSelection('Rearrange animators in timeline');
- }
- }
-
- if (Blockbench.isTouch) {
- timeout = setTimeout(() => {
- active = true;
- move(e1);
- }, 320)
- }
-
- addEventListeners(document, 'mousemove touchmove', move, {passive: false});
- addEventListeners(document, 'mouseup touchend', off, {passive: false});
- },
- dragKeyframes(clicked, e1) {
- if (e1.target.classList.contains('keyframe_bezier_handle')) return;
- if (e1.button > 0) return;
- convertTouchEvent(e1);
- let dragging_range;
- let dragging_restriction;
- let originalValue;
- let previousValue;
- let time_stretching;
- let values_changed;
- let is_setup = false;
- let old_bezier_values = {};
- let scope = this;
-
- function setup() {
- dragging_range = [Infinity, 0];
- dragging_restriction;
- originalValue;
- previousValue = 0;
- time_stretching = !Timeline.vue.graph_editor_open && (e1.ctrlOrCmd || Pressing.overrides.ctrl) && Timeline.selected.length > 1;
- values_changed = false;
-
- if (!clicked.selected && !e1.shiftKey && !Pressing.overrides.shift && Timeline.selected.length != 0) {
- clicked.select()
- } else if (clicked && !clicked.selected) {
- clicked.select({shiftKey: true})
- }
-
- Undo.initEdit({keyframes: Timeline.selected});
- Timeline.dragging_keyframes = true;
-
- for (var kf of Timeline.selected) {
- kf.time_before = kf.time;
- dragging_range[0] = Math.min(dragging_range[0], kf.time);
- dragging_range[1] = Math.max(dragging_range[1], kf.time);
- old_bezier_values[kf.uuid] = {
- left: kf.bezier_left_time.slice(),
- right: kf.bezier_right_time.slice(),
- }
- }
-
- if (Timeline.vue.graph_editor_open) {
- // Find dragging restriction
- dragging_restriction = [-Infinity, Infinity];
- let ba = scope.graph_editor_animator || 0;
- let all_keyframes = ba[scope.graph_editor_channel];
- if (all_keyframes) {
-
- let frst_keyframe;
- let last_keyframe;
- Timeline.selected.forEach(kf => {
- if (!frst_keyframe || frst_keyframe.time > kf.time) frst_keyframe = kf;
- if (!last_keyframe || last_keyframe.time < kf.time) last_keyframe = kf;
- })
- let prvs_keyframe;
- let next_keyframe;
- all_keyframes.forEach(kf => {
- if (kf.time < frst_keyframe.time && (!prvs_keyframe || prvs_keyframe.time < kf.time)) prvs_keyframe = kf;
- if (kf.time > last_keyframe.time && (!next_keyframe || next_keyframe.time > kf.time)) next_keyframe = kf;
- })
- if (prvs_keyframe) dragging_restriction[0] = prvs_keyframe.time;
- if (next_keyframe) dragging_restriction[1] = next_keyframe.time;
- }
- }
- is_setup = true;
- }
-
- function slide(e2) {
- convertTouchEvent(e2);
- e2.preventDefault();
- let offset = [
- e2.clientX - e1.clientX,
- e2.clientY - e1.clientY,
- ]
- if (!is_setup) {
- if (Math.pow(offset[0], 2) + Math.pow(offset[1], 2) > 40) {
- setup();
- } else {
- return;
- }
- }
-
- // Time
- let difference = 0;
- let max, min;
- let time_factor = 1;
- if ((!e2.ctrlOrCmd && !e2.shiftKey) || time_stretching || !Timeline.vue.graph_editor_open) {
- difference = Math.clamp(offset[0] / Timeline.vue._data.size, -256, 256);
- [min, max] = dragging_range;
-
- if (time_stretching) {
- time_factor = (clicked && clicked.time_before < (min + max) / 2)
- ? ((max-min-difference) / (max-min))
- : ((max-min+difference) / (max-min));
- time_factor = Math.roundTo(time_factor, 2);
- }
- }
-
- // Value
- let value = 0;
- let value_diff = 0;
- if (Timeline.vue.graph_editor_open) {
- value = -offset[1] / Timeline.vue.graph_size;
- var round_num = canvasGridSize(e2.shiftKey || Pressing.overrides.shift, e2.ctrlOrCmd || Pressing.overrides.ctrl);
- if (Toolbox.selected.id === 'resize_tool') {
- round_num *= 0.1;
- }
- value = Math.round(value/round_num)*round_num
- previousValue = previousValue == undefined ? value : previousValue;
- originalValue = originalValue == undefined ? value : originalValue;
-
- if (value !== previousValue) {
- value_diff = value - (previousValue||0);
- previousValue = value;
- }
- }
-
- for (let kf of Timeline.selected) {
- if (time_stretching) {
- if (clicked && clicked.time_before < (min + max) / 2) {
- var t = max - (kf.time_before - max) * -time_factor;
- } else {
- var t = min + (kf.time_before - min) * time_factor;
- }
- } else {
- var t = kf.time_before + difference;
- }
- let old_time = kf.time;
- if (dragging_restriction) {
- let step = Timeline.getStep();
- kf.time = Timeline.snapTime(Math.clamp(t, dragging_restriction[0] + step, dragging_restriction[1] - step));
- } else {
- kf.time = Timeline.snapTime(t);
- }
- if (old_time !== kf.time) {
- values_changed = true;
- }
- if (time_stretching && kf.interpolation == 'bezier') {
- let old_bezier = old_bezier_values[kf.uuid];
- kf.bezier_left_time.V3_set(old_bezier.left).V3_multiply(time_factor);
- kf.bezier_right_time.V3_set(old_bezier.right).V3_multiply(time_factor);
- }
-
- if (Timeline.vue.graph_editor_open && value_diff) {
- kf.offset(Timeline.vue.graph_editor_axis, value_diff);
- values_changed = true;
- }
- }
- if (time_stretching) {
- Blockbench.setStatusBarText(Math.round(time_factor * 100) + '%');
- } else {
- let text = trimFloatNumber(Math.round(difference * Animation.selected.snapping));
- if (Timeline.vue.graph_editor_open) {
- text += ` ⨉ ${trimFloatNumber(value - originalValue)}`
- }
- Blockbench.setStatusBarText(text);
- }
- BarItems.slider_keyframe_time.update()
- Animator.showMotionTrail(null, true)
- Animator.preview()
-
- }
- function off() {
- removeEventListeners(document, 'mousemove touchmove', slide);
- removeEventListeners(document, 'mouseup touchend', off);
-
- if (is_setup) {
- var deleted = []
- for (var kf of Timeline.selected) {
- delete kf.time_before;
- kf.replaceOthers(deleted);
- }
- Blockbench.setStatusBarText();
- if (values_changed) {
- Animation.selected.setLength();
- if (time_stretching) {
- Undo.finishEdit('Stretch keyframes');
- } else {
- Undo.addKeyframeCasualties(deleted);
- Undo.finishEdit('Drag keyframes');
- }
- } else {
- Undo.cancelEdit();
- }
- setTimeout(() => {
- Timeline.dragging_keyframes = false;
- }, 20);
- }
- }
- addEventListeners(document, 'mousemove touchmove', slide, {passive: false});
- addEventListeners(document, 'mouseup touchend', off);
- },
- dragBezierHandle(clicked, side, e1) {
- if (e1.button > 0) return;
- convertTouchEvent(e1);
- let values_changed;
- let is_setup = false;
- let axis_number = getAxisNumber(this.graph_editor_axis);
- let old_values = {};
- let lock_direction;
-
- function setup(offset) {
-
- if (!clicked.selected && !e1.shiftKey && !Pressing.overrides.shift && Timeline.selected.length != 0) {
- clicked.select()
- } else if (clicked && !clicked.selected) {
- clicked.select({shiftKey: true})
- }
- lock_direction = Math.abs(offset[0]) > Math.abs(offset[1]);
-
- Keyframe.selected.forEach(kf => {
- if (kf.interpolation == 'bezier') {
- old_values[kf.uuid] = {
- bezier_left_time: kf.bezier_left_time.slice(),
- bezier_left_value: kf.bezier_left_value.slice(),
- bezier_right_time: kf.bezier_right_time.slice(),
- bezier_right_value: kf.bezier_right_value.slice(),
- }
- }
- })
-
- Undo.initEdit({keyframes: Timeline.selected});
- Timeline.dragging_keyframes = true;
-
- is_setup = true;
- }
-
- function slide(e2) {
- convertTouchEvent(e2);
- e2.preventDefault();
- let offset = [
- e2.clientX - e1.clientX,
- e2.clientY - e1.clientY,
- ]
- if (!is_setup) {
- if (Math.pow(offset[0], 2) + Math.pow(offset[1], 2) > 20) {
- setup(offset);
- } else {
- return;
- }
- }
- let difference_time = Math.clamp(offset[0] / Timeline.vue._data.size, -256, 256);
- let difference_value = Math.clamp(-offset[1] / Timeline.vue.graph_size, -256, 256);
- if (e2.shiftKey || Pressing.overrides.shift) {
- if (lock_direction) {
- difference_value = 0;
- } else {
- difference_time = 0;
- }
- }
- if (e2.ctrlOrCmd || Pressing.overrides.ctrl) {
- let time_snap = Timeline.getStep();
- let val_snap = 0.25;
- difference_time = Math.round(difference_time / time_snap) * time_snap;
- difference_value = Math.round(difference_value / val_snap) * val_snap;
- }
-
- for (let kf of Timeline.selected) {
- if (kf.interpolation == 'bezier') {
-
- kf.bezier_left_time.V3_set(old_values[kf.uuid].bezier_left_time);
- kf.bezier_left_value.V3_set(old_values[kf.uuid].bezier_left_value);
- kf.bezier_right_time.V3_set(old_values[kf.uuid].bezier_right_time);
- kf.bezier_right_value.V3_set(old_values[kf.uuid].bezier_right_value);
-
- if (side === 'left') {
- kf.bezier_left_time[axis_number] = Math.min(0, old_values[kf.uuid].bezier_left_time[axis_number] + difference_time);
- kf.bezier_left_value[axis_number] = old_values[kf.uuid].bezier_left_value[axis_number] + difference_value;
- if (kf.bezier_linked) {
- kf.bezier_right_time[axis_number] = -kf.bezier_left_time[axis_number];
- kf.bezier_right_value[axis_number] = -kf.bezier_left_value[axis_number];
- }
- }
- if (side === 'right') {
- kf.bezier_right_time[axis_number] = Math.max(0, old_values[kf.uuid].bezier_right_time[axis_number] + difference_time);
- kf.bezier_right_value[axis_number] = old_values[kf.uuid].bezier_right_value[axis_number] + difference_value;
- if (kf.bezier_linked) {
- kf.bezier_left_time[axis_number] = -kf.bezier_right_time[axis_number];
- kf.bezier_left_value[axis_number] = -kf.bezier_right_value[axis_number];
- }
- }
- if (kf.uniform) {
- let off_axis_a = (axis_number+1) % 3;
- let off_axis_b = (axis_number+2) % 3;
- kf.bezier_right_time[off_axis_a] = kf.bezier_right_time[off_axis_b] = kf.bezier_right_time[axis_number];
- kf.bezier_right_value[off_axis_a] = kf.bezier_right_value[off_axis_b] = kf.bezier_right_value[axis_number];
- kf.bezier_left_time[off_axis_a] = kf.bezier_left_time[off_axis_b] = kf.bezier_left_time[axis_number];
- kf.bezier_left_value[off_axis_a] = kf.bezier_left_value[off_axis_b] = kf.bezier_left_value[axis_number];
- }
- values_changed = true;
- }
- }
- let text = `${trimFloatNumber(Math.roundTo(difference_time, 2))} ⨉ ${trimFloatNumber(Math.roundTo(difference_value, 2))}`;
- Blockbench.setStatusBarText(text);
-
- Timeline.vue.show_zero_line = !Timeline.vue.show_zero_line;
- Timeline.vue.show_zero_line = !Timeline.vue.show_zero_line;
- Animator.showMotionTrail(null, true)
- Animator.preview()
- }
- function off() {
- removeEventListeners(document, 'mousemove touchmove', slide);
- removeEventListeners(document, 'mouseup touchend', off);
-
- if (is_setup) {
- Blockbench.setStatusBarText();
- if (values_changed) {
- Undo.finishEdit('Adjust keyframe bezier handles');
- } else {
- Undo.cancelEdit();
- }
- setTimeout(() => {
- Timeline.dragging_keyframes = false;
- }, 20);
- }
- }
- addEventListeners(document, 'mousemove touchmove', slide, {passive: false});
- addEventListeners(document, 'mouseup touchend', off);
- },
- slideGraphAmplify(e1, anchor_side) {
- if (e1.button > 0) return;
- convertTouchEvent(e1);
- let original_values = {};
- let values_changed;
- let is_setup = false;
- let keyframes = this.graph_editor_animator[this.graph_editor_channel].filter(kf => kf.selected);
- let original_range = this.getSelectedGraphRange();
- let original_pixel_range = (original_range[1] - original_range[0]) * this.graph_size;
- let axis = this.graph_editor_axis;
-
- function setup() {
- Undo.initEdit({keyframes});
- values_changed = false;
- Timeline.dragging_keyframes = true;
- is_setup = true;
-
- for (let kf of keyframes) {
- original_values[kf.uuid] = kf.display_value || kf.get(axis);
- }
- }
-
- function slide(e2) {
- convertTouchEvent(e2);
- e2.preventDefault();
- let offset = e2.clientY - e1.clientY;
- if (anchor_side == 1) offset *= -1;
- if (!is_setup) {
- if (Math.abs(offset) > 4) {
- setup();
- } else {
- return;
- }
- }
-
- let value = 1 - offset / original_pixel_range;
- value = Math.round(value*100)/100;
-
- for (let kf of keyframes) {
- let origin = original_range[anchor_side];
- if (e2.altKey) {
- origin = Math.lerp(original_range[0], original_range[1], 0.5);
- }
- let target_value = (original_values[kf.uuid] - origin) * value + origin;
- kf.offset(axis, -kf.get(axis) + target_value);
- values_changed = true;
- }
- let text = Math.round(value * 100) + '%';
- Blockbench.setStatusBarText(text);
- Animator.showMotionTrail(null, true)
- Animator.preview()
-
- }
- function off() {
- removeEventListeners(document, 'mousemove touchmove', slide);
- removeEventListeners(document, 'mouseup touchend', off);
-
- if (is_setup) {
- Blockbench.setStatusBarText();
- if (values_changed) {
- Undo.finishEdit('Amplify keyframes');
- } else {
- Undo.cancelEdit();
- }
- setTimeout(() => {
- Timeline.dragging_keyframes = false;
- }, 20);
- }
- }
- addEventListeners(document, 'mousemove touchmove', slide, {passive: false});
- addEventListeners(document, 'mouseup touchend', off);
- },
- clickGraphEditor(event) {
- if (!this.show_other_graphs || !this.graph_editor_animator) return;
- let value = (this.graph_offset - event.offsetY) / this.graph_size;
- let original_time = Timeline.time;
-
- let tryAt = (x_coord) => {
- let time = (x_coord) / this.size;
- Timeline.time = time;
-
- let distances = ['x', 'y', 'z'].map(axis => {
- let axis_value = this.graph_editor_animator.interpolate(this.graph_editor_channel, false, axis);
- let diff = Math.abs(axis_value - value) * this.graph_size;
- if (diff < 12.5) {
- return {axis, diff};
- }
- }).filter(a => a);
- if (distances.length) return distances;
- }
-
- let real_x_coord = event.offsetX-8;
- let distances = tryAt(real_x_coord) || tryAt(real_x_coord + 4) || tryAt(real_x_coord - 4);
-
- if (distances) {
- distances.sort((a, b) => a.diff - b.diff);
- this.graph_editor_axis = distances[0].axis;
- }
- Timeline.time = original_time;
- },
- getBezierHandleStyle(keyframe, side) {
- let axis_number = getAxisNumber(this.graph_editor_axis);
- let x_offset = -keyframe[`bezier_${side}_time`][axis_number] * this.size;
- let y_offset = -keyframe[`bezier_${side}_value`][axis_number] * this.graph_size;
- let length = Math.sqrt(Math.pow(x_offset, 2) + Math.pow(y_offset, 2));
- let angle = Math.atan2(-y_offset, x_offset);
- return {
- right: x_offset + 'px',
- top: y_offset + 'px',
- '--length': Math.max(length - 6, 0) + 'px',
- '--angle': Math.radToDeg(angle) + 'deg',
- }
- },
- getSelectedGraphRange() {
- if (Keyframe.selected.length == 0 || !this.graph_editor_animator) return null;
- let keyframes = this.graph_editor_animator[this.graph_editor_channel];
- if (!keyframes || keyframes.length < 2) return null;
- let range = [Infinity, -Infinity];
- keyframes.forEach(kf => {
- if (!kf.selected) return;
- range[0] = Math.min(range[0], kf.display_value);
- range[1] = Math.max(range[1], kf.display_value);
- })
- if (range[0] == range[1]) return null;
- return range;
- },
- getNodeColor(node) {
- if (node.color >= 0) {
- return markerColors[node.color % markerColors.length].pastel;
- }
- return '';
- },
- clamp: Math.clamp,
- Condition,
- trimFloatNumber,
- getAxisLetter
- },
- watch: {
- size() {this.updateTimecodes()},
- length() {this.updateTimecodes()},
- scroll_left() {this.updateTimecodes()},
- },
- template: `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{animator.name}}
-
-
- remove
-
-
-
-
-
-
-
-
- {{ channel === 'sound' ? (animator.muted[channel] ? 'volume_off' : 'volume_up') : (animator.muted[channel] ? 'visibility_off' : 'visibility') }}
-
-
-
-
{{ channel_options.name }}
-
- {{ animator.rotation_global ? 'public' : 'public_off' }}
-
-
- add
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `
- }
+ component: TimelineComponent
})
Timeline.vue = Timeline.panel.inside_vue;
Timeline.panel.on('change_zindex', ({zindex}) => {
@@ -1930,7 +857,10 @@ BARS.defineActions(function() {
) {
Timeline.vue.graph_editor_channel = Timeline.selected[0].channel;
}
+
+ Timeline.vue.refreshTimelineCanvas();
}
+
})
new Toggle('graph_editor_other_graphs', {
icon: 'exposure_zero',
@@ -2138,6 +1068,7 @@ BARS.defineActions(function() {
ba.addToTimeline();
}
}
+ Timeline.vue.refreshTimelineCanvas();
}
})
new Action('add_all_to_timeline', {
@@ -2150,6 +1081,7 @@ BARS.defineActions(function() {
let ba = Animation.selected.getBoneAnimator(node);
if (ba) ba.addToTimeline();
})
+ Timeline.vue.refreshTimelineCanvas();
}
})
new Action('fold_all_animations', {
@@ -2160,7 +1092,7 @@ BARS.defineActions(function() {
for (var animator of Timeline.animators) {
animator.expanded = false;
}
-
+ Timeline.vue.refreshTimelineCanvas();
}
})
new Action('clear_timeline', {
@@ -2169,6 +1101,7 @@ BARS.defineActions(function() {
condition: {modes: ['animate']},
click: function () {
Timeline.vue._data.animators.purge();
+ Timeline.vue.clearTimelineCanvas();
unselectAllElements();
}
})
@@ -2212,6 +1145,7 @@ BARS.defineActions(function() {
click() {
Vue.set(channels, id, channels[id] == false);
StateMemory.save('timeline_channels');
+ Timeline.vue.refreshTimelineCanvas();
}
})
}
@@ -2222,6 +1156,7 @@ BARS.defineActions(function() {
{name: 'action.timeline_focus.hide_empty', icon: channels.hide_empty ? on : off, click() {
Vue.set(channels, 'hide_empty', !channels.hide_empty);
StateMemory.save('timeline_channels');
+ Timeline.vue.refreshTimelineCanvas();
}},
]
}
diff --git a/js/animations/timeline_animators.js b/js/animations/timeline_animators.js
index 47af99ff1..e5ad1a06f 100644
--- a/js/animations/timeline_animators.js
+++ b/js/animations/timeline_animators.js
@@ -128,6 +128,8 @@ export class GeneralAnimator {
if (undo) {
Undo.finishEdit('Add keyframe')
}
+
+ Timeline.vue.refreshTimelineCanvas();
return keyframe;
}
getOrMakeKeyframe(channel) {
diff --git a/js/util/array_util.js b/js/util/array_util.js
index ec57be548..49d335805 100644
--- a/js/util/array_util.js
+++ b/js/util/array_util.js
@@ -218,4 +218,7 @@ Array.prototype.V2_divide = function(x, y) {
this[0] /= parseFloat(x)||1;
this[1] /= parseFloat(y)||1;
return this;
+}
+Array.prototype.V2_toThree = function() {
+ return new THREE.Vector2(this[0], this[1]);
}
\ No newline at end of file
diff --git a/js/util/global.d.ts b/js/util/global.d.ts
index 2e7e2a107..f7f89aa89 100644
--- a/js/util/global.d.ts
+++ b/js/util/global.d.ts
@@ -130,6 +130,19 @@ declare global {
V3_divide(scalar: number): ArrayVector3
V3_toThree(): THREE.Vector3
V3_toEuler(): THREE.Euler
+
+ V2_set(x: number, y: number): ArrayVector2
+ V2_set(values: ArrayVector2): ArrayVector2
+ V2_add(x: number, y: number): ArrayVector2
+ V2_add(values: ArrayVector2): ArrayVector2
+ V2_subtract(x: number, y: number): ArrayVector2
+ V2_subtract(values: ArrayVector2): ArrayVector2
+ V2_multiply(x: number, y: number): ArrayVector2
+ V2_multiply(values: ArrayVector2): ArrayVector2
+ V2_divide(x: number, y: number): ArrayVector2
+ V2_divide(values: ArrayVector2): ArrayVector2
+ V2_toThree(): THREE.Vector2
+
}
interface Event {