载入中...
搜索中...
未找到
ActionTimelineSelection.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <optional>
5#include <utility>
6
7namespace eve::editor {
8namespace {
9
10void sortTrack(action::ActionTrack& track) {
11 std::stable_sort(track.notifies.begin(), track.notifies.end(), [](const auto& left, const auto& right) {
12 return left.time == right.time ? left.id.format() < right.id.format() : left.time < right.time;
13 });
14 std::stable_sort(track.states.begin(), track.states.end(), [](const auto& left, const auto& right) {
15 return left.start == right.start ? left.id.format() < right.id.format() : left.start < right.start;
16 });
17}
18
19void sortAnimationSections(action::ActionTimeline& timeline) {
20 std::stable_sort(
21 timeline.animationSections.begin(), timeline.animationSections.end(), [](const auto& left, const auto& right) {
22 return left.start == right.start ? left.id.format() < right.id.format() : left.start < right.start;
23 });
24}
25
26} // namespace
27
29 std::optional<TimelineSelectionRange> range;
30 const auto include = [&](Duration start, Duration end) {
31 if (!range) {
33 return;
34 }
35 range->start = std::min(range->start, start);
36 range->end = std::max(range->end, end);
37 };
38 for (const auto& section : target_.timeline().animationSections)
39 if (selection_.contains(section.id.format())) include(section.start, section.end);
40 for (const auto& track : target_.timeline().tracks) {
41 for (const auto& notify : track.notifies)
42 if (selection_.contains(notify.id.format())) include(notify.time, notify.time);
43 for (const auto& state : track.states)
44 if (selection_.contains(state.id.format())) include(state.start, state.end);
45 }
46 if (!range)
47 return eve::editing::failed<TimelineSelectionRange>(EditorStatus::Rejected,
48 RuleId("editor.action.timeline.selection-empty"),
49 "No timeline items are selected");
50 return eve::editing::applied<TimelineSelectionRange>(*range);
51}
52
54 if (selection_.empty()) return rejected("editor.action.timeline.selection-empty", "No timeline items are selected");
55 action::ActionTimeline candidate = target_.timeline();
56 const auto move = [&](Duration value) { return value.tryAdd(delta); };
57 for (auto& section : candidate.animationSections) {
58 if (!selection_.contains(section.id.format())) continue;
59 auto start = move(section.start);
60 auto end = move(section.end);
61 if (!start || !end)
62 return rejected("editor.action.timeline.time-overflow", "Selected animation-section time overflowed");
63 section.start = start.value();
64 section.end = end.value();
65 }
66 for (auto& track : candidate.tracks) {
67 const bool selectedNotify = std::any_of(track.notifies.begin(), track.notifies.end(),
68 [&](const auto& item) { return selection_.contains(item.id.format()); });
69 const bool selectedState = std::any_of(track.states.begin(), track.states.end(),
70 [&](const auto& item) { return selection_.contains(item.id.format()); });
71 if (track.locked && (selectedNotify || selectedState))
72 return rejected("editor.action.timeline.track-locked", "Selection contains an item on a locked track");
73 for (auto& notify : track.notifies) {
74 if (!selection_.contains(notify.id.format())) continue;
75 auto time = move(notify.time);
76 if (!time) return rejected("editor.action.timeline.time-overflow", "Selected notify time overflowed");
77 notify.time = time.value();
78 }
79 for (auto& state : track.states) {
80 if (!selection_.contains(state.id.format())) continue;
81 auto start = move(state.start);
82 auto end = move(state.end);
83 if (!start || !end)
84 return rejected("editor.action.timeline.time-overflow", "Selected notify-state time overflowed");
85 state.start = start.value();
86 state.end = end.value();
87 }
88 sortTrack(track);
89 }
90 sortAnimationSections(candidate);
91 return commit(std::move(candidate), "Move action timeline selection", "action.timeline.selection.move");
92}
93
95 auto range = selectionRange();
96 if (!range.ok()) return Result<void>::failure(range.status());
97 action::ActionTimeline candidate = target_.timeline();
98 const Duration anchor = range.value().start;
99 for (auto& section : candidate.animationSections) {
100 if (!selection_.contains(section.id.format())) continue;
101 const Duration span = Duration::fromNanoseconds(section.end.nanoseconds() - section.start.nanoseconds());
102 auto end = anchor.tryAdd(span);
103 if (!end)
104 return rejected("editor.action.timeline.time-overflow", "Aligned animation-section time overflowed");
105 section.start = anchor;
106 section.end = end.value();
107 }
108 for (auto& track : candidate.tracks) {
109 const bool touchesLocked = track.locked &&
110 (std::any_of(track.notifies.begin(), track.notifies.end(), [&](const auto& item) {
111 return selection_.contains(item.id.format());
112 }) ||
113 std::any_of(track.states.begin(), track.states.end(), [&](const auto& item) {
114 return selection_.contains(item.id.format());
115 }));
116 if (touchesLocked)
117 return rejected("editor.action.timeline.track-locked", "Selection contains an item on a locked track");
118 for (auto& notify : track.notifies)
119 if (selection_.contains(notify.id.format())) notify.time = anchor;
120 for (auto& state : track.states) {
121 if (!selection_.contains(state.id.format())) continue;
122 const Duration span = Duration::fromNanoseconds(state.end.nanoseconds() - state.start.nanoseconds());
123 auto end = anchor.tryAdd(span);
124 if (!end)
125 return rejected("editor.action.timeline.time-overflow", "Aligned notify-state time overflowed");
126 state.start = anchor;
127 state.end = end.value();
128 }
129 sortTrack(track);
130 }
131 sortAnimationSections(candidate);
132 return commit(std::move(candidate), "Align action timeline selection starts",
133 "action.timeline.selection.align-start");
134}
135
137 auto range = selectionRange();
138 if (!range.ok()) return Result<void>::failure(range.status());
139 action::ActionTimeline candidate = target_.timeline();
140 const Duration anchor = range.value().end;
141 for (auto& section : candidate.animationSections) {
142 if (!selection_.contains(section.id.format())) continue;
143 const auto span = section.end.nanoseconds() - section.start.nanoseconds();
144 auto start = anchor.tryAdd(Duration::fromNanoseconds(-span));
145 if (!start)
146 return rejected("editor.action.timeline.time-overflow", "Aligned animation-section time overflowed");
147 section.start = start.value();
148 section.end = anchor;
149 }
150 for (auto& track : candidate.tracks) {
151 const bool touchesLocked = track.locked &&
152 (std::any_of(track.notifies.begin(), track.notifies.end(), [&](const auto& item) {
153 return selection_.contains(item.id.format());
154 }) ||
155 std::any_of(track.states.begin(), track.states.end(), [&](const auto& item) {
156 return selection_.contains(item.id.format());
157 }));
158 if (touchesLocked)
159 return rejected("editor.action.timeline.track-locked", "Selection contains an item on a locked track");
160 for (auto& notify : track.notifies)
161 if (selection_.contains(notify.id.format())) notify.time = anchor;
162 for (auto& state : track.states) {
163 if (!selection_.contains(state.id.format())) continue;
164 const auto span = state.end.nanoseconds() - state.start.nanoseconds();
165 auto start = anchor.tryAdd(Duration::fromNanoseconds(-span));
166 if (!start)
167 return rejected("editor.action.timeline.time-overflow", "Aligned notify-state time overflowed");
168 state.start = start.value();
169 state.end = anchor;
170 }
171 sortTrack(track);
172 }
173 sortAnimationSections(candidate);
174 return commit(std::move(candidate), "Align action timeline selection ends",
175 "action.timeline.selection.align-end");
176}
177
179 auto range = selectionRange();
180 if (!range.ok()) return Result<void>::failure(range.status());
181 if (start < Duration::zero() || end <= start || end > target_.timeline().duration)
182 return rejected("editor.action.timeline.selection-scale-range",
183 "Scaled selection range must be ordered and inside the timeline");
184 const auto oldSpan = range.value().end.nanoseconds() - range.value().start.nanoseconds();
185 if (oldSpan <= 0)
186 return rejected("editor.action.timeline.selection-scale-point",
187 "A point-only selection cannot be proportionally scaled");
188 const double rate = static_cast<double>(end.nanoseconds() - start.nanoseconds()) /
189 static_cast<double>(oldSpan);
190 const auto map = [&](Duration value) -> Result<Duration> {
191 const Duration offset = Duration::fromNanoseconds(value.nanoseconds() - range.value().start.nanoseconds());
192 auto scaled = offset.scaled(rate);
193 if (!scaled) return scaled;
194 return start.tryAdd(scaled.value());
195 };
196 action::ActionTimeline candidate = target_.timeline();
197 for (auto& section : candidate.animationSections) {
198 if (!selection_.contains(section.id.format())) continue;
199 auto mappedStart = map(section.start);
200 auto mappedEnd = map(section.end);
201 auto blend = section.blendIn.scaled(rate);
202 if (!mappedStart || !mappedEnd || !blend)
203 return rejected("editor.action.timeline.selection-scale-overflow", "Scaled selection time overflowed");
204 section.start = mappedStart.value();
205 section.end = mappedEnd.value();
206 section.blendIn = blend.value();
207 }
208 for (auto& track : candidate.tracks) {
209 const bool touchesLocked = track.locked &&
210 (std::any_of(track.notifies.begin(), track.notifies.end(), [&](const auto& item) {
211 return selection_.contains(item.id.format());
212 }) ||
213 std::any_of(track.states.begin(), track.states.end(), [&](const auto& item) {
214 return selection_.contains(item.id.format());
215 }));
216 if (touchesLocked)
217 return rejected("editor.action.timeline.track-locked", "Selection contains an item on a locked track");
218 for (auto& notify : track.notifies) {
219 if (!selection_.contains(notify.id.format())) continue;
220 auto mapped = map(notify.time);
221 if (!mapped)
222 return rejected("editor.action.timeline.selection-scale-overflow", "Scaled notify time overflowed");
223 notify.time = mapped.value();
224 }
225 for (auto& state : track.states) {
226 if (!selection_.contains(state.id.format())) continue;
227 auto mappedStart = map(state.start);
228 auto mappedEnd = map(state.end);
229 if (!mappedStart || !mappedEnd)
230 return rejected("editor.action.timeline.selection-scale-overflow",
231 "Scaled notify-state time overflowed");
232 state.start = mappedStart.value();
233 state.end = mappedEnd.value();
234 }
235 sortTrack(track);
236 }
237 sortAnimationSections(candidate);
238 return commit(std::move(candidate), "Scale action timeline selection", "action.timeline.selection.scale");
239}
240
241} // namespace eve::editor
double value
UI-neutral transactional action timeline editor and preview model.
Duration start
Vec3 anchor
Definition CaveMesh.cpp:90
HexVec3 left
HexVec3 right
float blend
size_t offset
Range range
Signed, fixed-resolution simulation duration in nanoseconds.
Definition Time.h:43
constexpr std::int64_t nanoseconds() const noexcept
Return the exact signed nanosecond representation.
Definition Time.h:69
static constexpr Duration fromNanoseconds(std::int64_t nanoseconds) noexcept
Construct an exact duration from nanoseconds.
Definition Time.h:55
static constexpr Duration zero() noexcept
Return zero duration.
Definition Time.h:60
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Result< void > alignSelectionStart()
Align selected item starts to the earliest selected start while preserving item durations.
Result< void > alignSelectionEnd()
Align selected item ends to the latest selected end while preserving item durations.
Result< TimelineSelectionRange > selectionRange() const
Return the outer bounds of every selected section, notify and notify-state.
Result< void > moveSelection(Duration delta)
Move the complete selection by one exact delta in a single undoable transaction.
Result< void > scaleSelection(Duration start, Duration end)
Proportionally fit the complete selection into exact destination bounds.
const action::ActionTimeline & timeline() const noexcept
Borrow the authoritative timeline until the next mutation.
eve::editing::RuleId RuleId
Definition EditorIds.h:32
Canonical action timeline asset shared by runtime and editor.
std::vector< ActionAnimationSection > animationSections
std::vector< ActionTrack > tracks
Exact outer time bounds of the current owning timeline selection.