939 lines
54 KiB
C++
939 lines
54 KiB
C++
#include "action/editor/ActionTimelineScriptBindings.h"
|
|
|
|
#include "action/ActionNotifyRegistry.h"
|
|
#include "action/editor/ActionEditorModule.h"
|
|
#include "action/editor/ActionTimelineEditor.h"
|
|
#include "action/editor/ActionTimelineWidget.h"
|
|
#include "animation/AnimClip.h"
|
|
#include "animation/AnimImporter.h"
|
|
#include "animation/AnimPose.h"
|
|
#include "animation/MontagePlayer.h"
|
|
#include "common/SquirrelBinding.h"
|
|
#include "common/SquirrelOwnership.h"
|
|
#include "editor/EditorWorkspace.h"
|
|
#include "model3d/ModelData.h"
|
|
|
|
#include <simplesquirrel/simplesquirrel.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
namespace eve::editor {
|
|
namespace {
|
|
|
|
constexpr const char* kBindingSource = "editor.action.timeline.squirrel";
|
|
|
|
Status statusFrom(const EditorResult<void>& result) { return result.status(); }
|
|
|
|
template <class T>
|
|
Status statusFrom(const EditorResult<T>& result) {
|
|
return result.status();
|
|
}
|
|
|
|
ssq::Table project(HSQUIRRELVM vm, const EditorResult<void>& result) {
|
|
return script::projectStatusResult(vm, statusFrom(result), result.ok(), false);
|
|
}
|
|
|
|
template <class T>
|
|
ssq::Table project(HSQUIRRELVM vm, const EditorResult<T>& result, Value value) {
|
|
const bool hasValue = result.ok();
|
|
return script::projectStatusResult(vm, statusFrom(result), hasValue, hasValue, value);
|
|
}
|
|
|
|
ssq::Table bindingFailure(HSQUIRRELVM vm, DiagnosticCode code, std::string message, std::string path = {}) {
|
|
return script::projectStatusResult(
|
|
vm, Status::failure(Diagnostic::error(code, std::move(message), std::move(path), {}, kBindingSource)), false,
|
|
false);
|
|
}
|
|
|
|
Result<Duration> seconds(float value) {
|
|
auto converted = Duration::fromSeconds(static_cast<double>(value));
|
|
if (!converted) return Result<Duration>::failure(converted.status());
|
|
return Result<Duration>::success(std::move(converted).takeValue());
|
|
}
|
|
|
|
const action::ActionTrack* trackAt(const action::ActionTimeline& timeline, int index) {
|
|
if (index < 0 || static_cast<std::size_t>(index) >= timeline.tracks.size()) return nullptr;
|
|
return &timeline.tracks[static_cast<std::size_t>(index)];
|
|
}
|
|
|
|
const action::ActionAnimationSection* sectionAt(const action::ActionTimeline& timeline, int index) {
|
|
if (index < 0 || static_cast<std::size_t>(index) >= timeline.animationSections.size()) return nullptr;
|
|
return &timeline.animationSections[static_cast<std::size_t>(index)];
|
|
}
|
|
|
|
const TimelineItemGeometry* itemAt(const TimelineWidgetLayout& layout, int index) {
|
|
if (index < 0 || static_cast<std::size_t>(index) >= layout.items.size()) return nullptr;
|
|
return &layout.items[static_cast<std::size_t>(index)];
|
|
}
|
|
|
|
const action::ActionNotifyState* findState(const action::ActionTimeline& timeline, const std::string& itemId) {
|
|
for (const auto& track : timeline.tracks) {
|
|
const auto found = std::find_if(track.states.begin(), track.states.end(),
|
|
[&](const auto& state) { return state.id.format() == itemId; });
|
|
if (found != track.states.end()) return &*found;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Value montageAdvanceValue(const animation::MontageAdvance& value) {
|
|
Value::Object projected;
|
|
projected["previousSeconds"] = value.previous.seconds();
|
|
projected["currentSeconds"] = value.current.seconds();
|
|
projected["sectionId"] = value.sectionId ? value.sectionId->format() : "";
|
|
projected["eventCount"] = static_cast<std::int64_t>(value.events.size());
|
|
projected["activeBlockCount"] = static_cast<std::int64_t>(value.activeBlocks.size());
|
|
projected["rootMotionX"] = static_cast<double>(value.rootMotion.px);
|
|
projected["rootMotionY"] = static_cast<double>(value.rootMotion.py);
|
|
projected["rootMotionZ"] = static_cast<double>(value.rootMotion.pz);
|
|
projected["weight"] = value.weight;
|
|
projected["completed"] = value.completed;
|
|
return Value(std::move(projected));
|
|
}
|
|
|
|
void accumulateRootMotion(animation::TransformTRS& total, const animation::TransformTRS& delta) {
|
|
total.px += delta.px;
|
|
total.py += delta.py;
|
|
total.pz += delta.pz;
|
|
const float tx = total.qx;
|
|
const float ty = total.qy;
|
|
const float tz = total.qz;
|
|
const float tw = total.qw;
|
|
total.qx = tw * delta.qx + tx * delta.qw + ty * delta.qz - tz * delta.qy;
|
|
total.qy = tw * delta.qy - tx * delta.qz + ty * delta.qw + tz * delta.qx;
|
|
total.qz = tw * delta.qz + tx * delta.qy - ty * delta.qx + tz * delta.qw;
|
|
total.qw = tw * delta.qw - tx * delta.qx - ty * delta.qy - tz * delta.qz;
|
|
}
|
|
|
|
class ScriptActionTimelineEditor {
|
|
public:
|
|
ScriptActionTimelineEditor(std::string targetId, action::ActionTimeline timeline,
|
|
action::ActionNotifyRegistry registry)
|
|
: registry_(std::move(registry)),
|
|
editor_(std::move(targetId), std::move(timeline)),
|
|
widget_(editor_, registry_) {}
|
|
|
|
ActionTimelineEditor& editor() noexcept { return editor_; }
|
|
ActionTimelineWidget& widget() noexcept { return widget_; }
|
|
|
|
const ActionTimelineEditor& editor() const noexcept { return editor_; }
|
|
const ActionTimelineWidget& widget() const noexcept { return widget_; }
|
|
|
|
[[nodiscard]] Result<void> registerRuntimeClip(std::string uri, model3d::ModelData& model,
|
|
animation::AnimSkeleton& skeleton, int animationIndex) {
|
|
if (uri.empty())
|
|
return Result<void>::failure(
|
|
Diagnostic::error(DiagnosticCode::InvalidArgument, "montage clip URI must not be empty", "uri"));
|
|
if (animationIndex < 0 || animationIndex >= animation::AnimImporter::getAnimationCountFromModel(&model))
|
|
return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
|
|
"montage animation index is outside the model", "index"));
|
|
std::unique_ptr<animation::AnimClip> clip(
|
|
animation::AnimImporter::loadClipFromModel(&model, &skeleton, animationIndex));
|
|
if (!clip)
|
|
return Result<void>::failure(
|
|
Diagnostic::error(DiagnosticCode::Failed, "montage clip import produced no animation", uri));
|
|
if (montage_) return montage_->replaceClip(uri, std::move(clip));
|
|
const auto found = std::find_if(runtimeClips_.begin(), runtimeClips_.end(),
|
|
[&](const auto& asset) { return asset.uri == uri; });
|
|
if (found == runtimeClips_.end())
|
|
runtimeClips_.push_back({std::move(uri), std::move(clip)});
|
|
else
|
|
found->clip = std::move(clip);
|
|
return Result<void>::success(Status::success(StatusCode::Applied));
|
|
}
|
|
|
|
[[nodiscard]] Result<void> beginRuntime(animation::AnimSkeleton& skeleton) {
|
|
auto montage = std::make_unique<animation::MontagePlayer>(skeleton);
|
|
auto timeline = editor_.target().timeline();
|
|
auto prepared = montage->prepare(timeline, std::move(runtimeClips_));
|
|
if (!prepared) return Result<void>::failure(prepared.status());
|
|
runtimeTimeline_ = timeline;
|
|
montage_ = std::move(montage);
|
|
tick_ = SimulationTick::zero();
|
|
runtimeAdvance_.reset();
|
|
runtimeRate_ = timeline.montage.basePlayRate;
|
|
runtimePaused_ = false;
|
|
sectionRates_.clear();
|
|
return restartExecution();
|
|
}
|
|
|
|
[[nodiscard]] Result<animation::MontageAdvance> advanceRuntime(Duration delta) {
|
|
if (!runtime_ || !montage_)
|
|
return Result<animation::MontageAdvance>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
if (montage_->isBlendingOut()) {
|
|
tick_ = SimulationTick(tick_.value() + 1);
|
|
auto settled = montage_->advanceBlendOut(delta, tick_);
|
|
if (settled) runtimeAdvance_ = settled.value();
|
|
return settled;
|
|
}
|
|
if (runtimePaused_) {
|
|
animation::MontageAdvance paused;
|
|
paused.previous = paused.current = montage_->time();
|
|
paused.weight = montage_->weight();
|
|
return Result<animation::MontageAdvance>::success(std::move(paused), Status::success(StatusCode::NoOp));
|
|
}
|
|
double sectionRate = 1.0;
|
|
auto sectionIndex = montage_->physicalSectionIndex();
|
|
if (sectionIndex) {
|
|
const auto found = sectionRates_.find(sectionIndex.value());
|
|
if (found != sectionRates_.end()) sectionRate = found->second;
|
|
}
|
|
auto scaledDelta = Duration::fromSeconds(delta.seconds() * runtimeRate_ * sectionRate);
|
|
if (!scaledDelta) return Result<animation::MontageAdvance>::failure(scaledDelta.status());
|
|
Duration remainingDelta = std::move(scaledDelta).takeValue();
|
|
animation::MontageAdvance combined;
|
|
combined.previous = montage_->time();
|
|
bool firstSlice = true;
|
|
while (true) {
|
|
Duration remainingTimeline =
|
|
Duration::fromNanoseconds(runtimeTimeline_->duration.nanoseconds() - montage_->time().nanoseconds());
|
|
if (runtimeTimeline_->montage.looping && remainingTimeline.isZero()) {
|
|
auto restarted = restartExecution();
|
|
if (!restarted) return Result<animation::MontageAdvance>::failure(restarted.status());
|
|
remainingTimeline = runtimeTimeline_->duration;
|
|
}
|
|
const Duration slice =
|
|
runtimeTimeline_->montage.looping ? std::min(remainingDelta, remainingTimeline) : remainingDelta;
|
|
tick_ = SimulationTick(tick_.value() + 1);
|
|
auto actionAdvance = runtime_->advance(executionId_, tick_, slice);
|
|
if (!actionAdvance) return Result<animation::MontageAdvance>::failure(actionAdvance.status());
|
|
auto presented = montage_->present(actionAdvance.value(), tick_);
|
|
if (!presented) return Result<animation::MontageAdvance>::failure(presented.status());
|
|
auto value = std::move(presented).takeValue();
|
|
if (firstSlice) {
|
|
combined = std::move(value);
|
|
firstSlice = false;
|
|
} else {
|
|
combined.current = value.current;
|
|
combined.sectionId = value.sectionId;
|
|
combined.events.insert(combined.events.end(), std::make_move_iterator(value.events.begin()),
|
|
std::make_move_iterator(value.events.end()));
|
|
combined.activeBlocks = std::move(value.activeBlocks);
|
|
accumulateRootMotion(combined.rootMotion, value.rootMotion);
|
|
combined.weight = value.weight;
|
|
combined.completed = value.completed;
|
|
}
|
|
if (!runtimeTimeline_->montage.looping || remainingDelta <= slice) break;
|
|
|
|
remainingDelta = Duration::fromNanoseconds(remainingDelta.nanoseconds() - slice.nanoseconds());
|
|
auto restarted = restartExecution();
|
|
if (!restarted) return Result<animation::MontageAdvance>::failure(restarted.status());
|
|
}
|
|
if (runtimeTimeline_->montage.looping && combined.completed) {
|
|
auto restarted = restartExecution();
|
|
if (!restarted) return Result<animation::MontageAdvance>::failure(restarted.status());
|
|
combined.current = Duration::zero();
|
|
combined.sectionId.reset();
|
|
combined.activeBlocks.clear();
|
|
combined.completed = false;
|
|
combined.weight = montage_->weight();
|
|
}
|
|
runtimeAdvance_ = combined;
|
|
return Result<animation::MontageAdvance>::success(std::move(combined), Status::success(StatusCode::Pending));
|
|
}
|
|
|
|
[[nodiscard]] Result<animation::MontageAdvance> jumpRuntime(Duration target) {
|
|
if (!runtimeTimeline_ || !montage_)
|
|
return Result<animation::MontageAdvance>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
auto restarted = restartExecution(false);
|
|
if (!restarted) return Result<animation::MontageAdvance>::failure(restarted.status());
|
|
auto rebound = montage_->rebindExecution(executionId_);
|
|
if (!rebound) return Result<animation::MontageAdvance>::failure(rebound.status());
|
|
tick_ = SimulationTick(tick_.value() + 1);
|
|
auto advanced = runtime_->advance(executionId_, tick_, target);
|
|
if (!advanced) return Result<animation::MontageAdvance>::failure(advanced.status());
|
|
auto jumped = montage_->jumpToTime(executionId_, target, tick_);
|
|
if (jumped) runtimeAdvance_ = jumped.value();
|
|
return jumped;
|
|
}
|
|
|
|
[[nodiscard]] Result<animation::MontageAdvance> jumpRuntimeSection(std::size_t index) {
|
|
if (!runtimeTimeline_)
|
|
return Result<animation::MontageAdvance>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
auto range = runtimeTimeline_->sectionRange(index);
|
|
if (!range) return Result<animation::MontageAdvance>::failure(range.status());
|
|
return jumpRuntime(range.value().first);
|
|
}
|
|
|
|
[[nodiscard]] Result<void> syncRuntimeSection(std::size_t index, Duration targetDuration) {
|
|
if (!runtimeTimeline_)
|
|
return Result<void>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
if (targetDuration <= Duration::zero())
|
|
return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
|
|
"target section duration must be positive", "duration"));
|
|
auto range = runtimeTimeline_->sectionRange(index);
|
|
if (!range) return Result<void>::failure(range.status());
|
|
const double raw = (range.value().second.seconds() - range.value().first.seconds());
|
|
sectionRates_[index] = raw / targetDuration.seconds();
|
|
return Result<void>::success(Status::success(StatusCode::Applied));
|
|
}
|
|
|
|
[[nodiscard]] Result<animation::MontageAdvance> beginRuntimeBlendOut(Duration duration) {
|
|
if (!montage_)
|
|
return Result<animation::MontageAdvance>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
tick_ = SimulationTick(tick_.value() + 1);
|
|
auto stopped = montage_->beginBlendOut(duration, tick_);
|
|
if (stopped) runtimeAdvance_ = stopped.value();
|
|
return stopped;
|
|
}
|
|
|
|
void setRuntimePaused(bool paused) noexcept { runtimePaused_ = paused; }
|
|
[[nodiscard]] bool runtimePaused() const noexcept { return runtimePaused_; }
|
|
|
|
[[nodiscard]] Result<void> cancelRuntime() {
|
|
if (!runtime_ || !montage_) return Result<void>::success(Status::success(StatusCode::NoOp));
|
|
tick_ = SimulationTick(tick_.value() + 1);
|
|
auto cancelled = runtime_->cancel(executionId_, tick_);
|
|
if (!cancelled) return cancelled;
|
|
montage_->stop();
|
|
return Result<void>::success(Status::success(StatusCode::Applied));
|
|
}
|
|
|
|
[[nodiscard]] animation::AnimPose* runtimePose() noexcept { return montage_ ? &montage_->pose() : nullptr; }
|
|
[[nodiscard]] const std::optional<animation::MontageAdvance>& runtimeAdvance() const noexcept {
|
|
return runtimeAdvance_;
|
|
}
|
|
[[nodiscard]] bool runtimePlaying() const noexcept { return montage_ && montage_->isPlaying(); }
|
|
|
|
[[nodiscard]] Result<std::size_t> runtimePhysicalSection() const {
|
|
if (!montage_)
|
|
return Result<std::size_t>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
return montage_->physicalSectionIndex();
|
|
}
|
|
|
|
[[nodiscard]] Result<double> runtimeSectionProgress(std::size_t index) const {
|
|
if (!montage_)
|
|
return Result<double>::failure(
|
|
Diagnostic::error(DiagnosticCode::Conflict, "montage runtime has not been started", "runtime"));
|
|
return montage_->physicalSectionProgress(index);
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] action::ActionDefinition runtimeDefinition() const {
|
|
action::ActionDefinition definition;
|
|
definition.id = runtimeTimeline_->actionId;
|
|
if (runtimeTimeline_->splitTimestamps.size() >= 2) {
|
|
definition.timing.windup = runtimeTimeline_->splitTimestamps[0];
|
|
definition.timing.active = Duration::fromNanoseconds(runtimeTimeline_->splitTimestamps[1].nanoseconds() -
|
|
runtimeTimeline_->splitTimestamps[0].nanoseconds());
|
|
definition.timing.recover = Duration::fromNanoseconds(runtimeTimeline_->duration.nanoseconds() -
|
|
runtimeTimeline_->splitTimestamps[1].nanoseconds());
|
|
} else {
|
|
definition.timing.active = runtimeTimeline_->duration;
|
|
}
|
|
definition.timeline = *runtimeTimeline_;
|
|
return definition;
|
|
}
|
|
|
|
[[nodiscard]] Result<void> restartExecution(bool playMontage = true) {
|
|
auto runtime = std::make_unique<action::ActionRuntime>();
|
|
auto definition = runtimeDefinition();
|
|
action::ActionRequest request;
|
|
request.actionId = definition.id;
|
|
auto submitted = runtime->submit(std::move(definition), std::move(request));
|
|
if (!submitted) return Result<void>::failure(submitted.status());
|
|
if (playMontage) {
|
|
auto played = montage_->play(submitted.value());
|
|
if (!played) return Result<void>::failure(played.status());
|
|
}
|
|
runtime_ = std::move(runtime);
|
|
executionId_ = submitted.value();
|
|
return Result<void>::success(Status::success(StatusCode::Applied));
|
|
}
|
|
|
|
action::ActionNotifyRegistry registry_;
|
|
ActionTimelineEditor editor_;
|
|
ActionTimelineWidget widget_;
|
|
std::vector<animation::MontageClipAsset> runtimeClips_;
|
|
std::unique_ptr<action::ActionRuntime> runtime_;
|
|
std::unique_ptr<animation::MontagePlayer> montage_;
|
|
action::ActionExecutionId executionId_{};
|
|
SimulationTick tick_ = SimulationTick::zero();
|
|
std::optional<animation::MontageAdvance> runtimeAdvance_;
|
|
std::optional<action::ActionTimeline> runtimeTimeline_;
|
|
std::unordered_map<std::size_t, double> sectionRates_;
|
|
double runtimeRate_ = 1.0;
|
|
bool runtimePaused_ = false;
|
|
};
|
|
|
|
std::string eventKind(action::ActionTimelineEventKind kind) {
|
|
switch (kind) {
|
|
case action::ActionTimelineEventKind::Notify: return "notify";
|
|
case action::ActionTimelineEventKind::StateEnter: return "state_enter";
|
|
case action::ActionTimelineEventKind::StateExit: return "state_exit";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void exposeActionTimelineScriptBindings(ssq::Table& table, ssq::Class& moduleClass) {
|
|
const HSQUIRRELVM vm = table.getHandle();
|
|
auto actionEditor = table.addClass<ScriptActionTimelineEditor>(
|
|
"ActionTimelineEditor",
|
|
std::function<ScriptActionTimelineEditor*()>([]() -> ScriptActionTimelineEditor* { return nullptr; }), true);
|
|
|
|
actionEditor.addFunc("configureWorkspace", [vm](ScriptActionTimelineEditor* self, EditorWorkspace* workspace) {
|
|
if (!self || !workspace)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument,
|
|
"action timeline editor and workspace must not be null", "workspace");
|
|
return project(vm, self->editor().configureWorkspace(*workspace));
|
|
});
|
|
actionEditor.addFunc(
|
|
"setViewport", [vm](ScriptActionTimelineEditor* self, float width, float rowHeight, float labelWidth) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return project(vm, self->widget().setViewport(width, rowHeight, labelWidth));
|
|
});
|
|
actionEditor.addFunc("addTrack", [vm](ScriptActionTimelineEditor* self, const std::string& trackId,
|
|
const std::string& label, const std::string& kind) {
|
|
if (!self) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor is null");
|
|
auto parsedId = LogicalId::parse(trackId);
|
|
auto parsedKind = action::parseActionTrackKind(kind);
|
|
if (!parsedId) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "track id is invalid", "trackId");
|
|
if (!parsedKind) return script::projectStatusResult(vm, parsedKind.status(), false, false);
|
|
action::ActionTrack track;
|
|
track.id = std::move(*parsedId);
|
|
track.label = label;
|
|
track.kind = parsedKind.value();
|
|
return project(vm, self->editor().addTrack(std::move(track)));
|
|
});
|
|
actionEditor.addFunc("removeTrack", [vm](ScriptActionTimelineEditor* self, const std::string& trackId) {
|
|
if (!self) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor is null");
|
|
auto parsed = LogicalId::parse(trackId);
|
|
if (!parsed) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "track id is invalid", "trackId");
|
|
return project(vm, self->editor().removeTrack(*parsed));
|
|
});
|
|
actionEditor.addFunc(
|
|
"renameTrack", [vm](ScriptActionTimelineEditor* self, const std::string& trackId, const std::string& label) {
|
|
if (!self) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor is null");
|
|
auto parsed = LogicalId::parse(trackId);
|
|
if (!parsed) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "track id is invalid", "trackId");
|
|
return project(vm, self->editor().renameTrack(*parsed, label));
|
|
});
|
|
actionEditor.addFunc(
|
|
"setTrackMuted", [vm](ScriptActionTimelineEditor* self, const std::string& trackId, bool muted) {
|
|
if (!self) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor is null");
|
|
auto parsed = LogicalId::parse(trackId);
|
|
if (!parsed) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "track id is invalid", "trackId");
|
|
return project(vm, self->editor().setTrackMuted(*parsed, muted));
|
|
});
|
|
actionEditor.addFunc(
|
|
"setTrackLocked", [vm](ScriptActionTimelineEditor* self, const std::string& trackId, bool locked) {
|
|
if (!self) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor is null");
|
|
auto parsed = LogicalId::parse(trackId);
|
|
if (!parsed) return bindingFailure(vm, DiagnosticCode::InvalidArgument, "track id is invalid", "trackId");
|
|
return project(vm, self->editor().setTrackLocked(*parsed, locked));
|
|
});
|
|
actionEditor.addFunc("setSnapSeconds", [vm](ScriptActionTimelineEditor* self, float intervalSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto interval = seconds(intervalSeconds);
|
|
if (!interval) return script::projectStatusResult(vm, interval.status(), false, false);
|
|
return project(vm, self->widget().setSnapInterval(std::move(interval).takeValue()));
|
|
});
|
|
actionEditor.addFunc(
|
|
"setVisibleSeconds", [vm](ScriptActionTimelineEditor* self, float startSeconds, float endSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto start = seconds(startSeconds);
|
|
auto end = seconds(endSeconds);
|
|
if (!start) return script::projectStatusResult(vm, start.status(), false, false);
|
|
if (!end) return script::projectStatusResult(vm, end.status(), false, false);
|
|
return project(vm, self->widget().setVisibleRange(start.value(), end.value()));
|
|
});
|
|
actionEditor.addFunc("zoomTimeline", [vm](ScriptActionTimelineEditor* self, float factor, float anchor) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return project(vm, self->widget().zoom(factor, anchor));
|
|
});
|
|
actionEditor.addFunc("panTimelineSeconds", [vm](ScriptActionTimelineEditor* self, float deltaSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto delta = seconds(deltaSeconds);
|
|
if (!delta) return script::projectStatusResult(vm, delta.status(), false, false);
|
|
return project(vm, self->widget().pan(delta.value()));
|
|
});
|
|
actionEditor.addFunc("pointerDown", [vm](ScriptActionTimelineEditor* self, float x, float y, bool additive) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return project(vm, self->widget().pointerDown(x, y, additive));
|
|
});
|
|
actionEditor.addFunc("pointerMove", [vm](ScriptActionTimelineEditor* self, float x) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return project(vm, self->widget().pointerMove(x));
|
|
});
|
|
actionEditor.addFunc("pointerUp", [vm](ScriptActionTimelineEditor* self, float x) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return project(vm, self->widget().pointerUp(x));
|
|
});
|
|
actionEditor.addFunc("seekX", [vm](ScriptActionTimelineEditor* self, float x) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return project(vm, self->widget().seek(x));
|
|
});
|
|
actionEditor.addFunc("seekSeconds", [vm](ScriptActionTimelineEditor* self, float value) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto duration = seconds(value);
|
|
if (!duration) return script::projectStatusResult(vm, duration.status(), false, false);
|
|
return project(vm, self->editor().seek(std::move(duration).takeValue()));
|
|
});
|
|
actionEditor.addFunc("resizeState", [vm](ScriptActionTimelineEditor* self, const std::string& itemId,
|
|
float startSeconds, float endSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto start = seconds(startSeconds);
|
|
if (!start) return script::projectStatusResult(vm, start.status(), false, false);
|
|
auto end = seconds(endSeconds);
|
|
if (!end) return script::projectStatusResult(vm, end.status(), false, false);
|
|
auto parsedItemId = LogicalId::parse(itemId);
|
|
if (!parsedItemId)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "state item id is not canonical", "itemId");
|
|
return project(
|
|
vm, self->editor().resizeState(*parsedItemId, std::move(start).takeValue(), std::move(end).takeValue()));
|
|
});
|
|
actionEditor.addFunc("undo", [vm](ScriptActionTimelineEditor* self) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto result = self->editor().undo();
|
|
return project(vm, result, Value(result.ok() ? static_cast<std::int64_t>(result.value().afterRevision) : 0));
|
|
});
|
|
actionEditor.addFunc("redo", [vm](ScriptActionTimelineEditor* self) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto result = self->editor().redo();
|
|
return project(vm, result, Value(result.ok() ? static_cast<std::int64_t>(result.value().afterRevision) : 0));
|
|
});
|
|
actionEditor.addFunc("update", [vm](ScriptActionTimelineEditor* self, float deltaSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto delta = seconds(deltaSeconds);
|
|
if (!delta) return script::projectStatusResult(vm, delta.status(), false, false);
|
|
auto result = self->editor().update(std::move(delta).takeValue());
|
|
return project(vm, result, Value(result.ok() ? static_cast<std::int64_t>(result.value()) : 0));
|
|
});
|
|
actionEditor.addFunc("snapshot", [vm](ScriptActionTimelineEditor* self) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return script::projectResult(vm, self->editor().target().timeline().toValue(),
|
|
[](Value value) { return value; });
|
|
});
|
|
actionEditor.addFunc(
|
|
"registerRuntimeClip", [vm](ScriptActionTimelineEditor* self, const std::string& uri, model3d::ModelData* model,
|
|
animation::AnimSkeleton* skeleton, int animationIndex) {
|
|
if (!self || !model || !skeleton)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument,
|
|
"editor, model and skeleton must not be null", "runtimeClip");
|
|
return script::projectResult(vm, self->registerRuntimeClip(uri, *model, *skeleton, animationIndex));
|
|
});
|
|
actionEditor.addFunc("beginRuntime", [vm](ScriptActionTimelineEditor* self, animation::AnimSkeleton* skeleton) {
|
|
if (!self || !skeleton)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "editor and skeleton must not be null",
|
|
"skeleton");
|
|
return script::projectResult(vm, self->beginRuntime(*skeleton));
|
|
});
|
|
actionEditor.addFunc("advanceRuntime", [vm](ScriptActionTimelineEditor* self, float deltaSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto delta = seconds(deltaSeconds);
|
|
if (!delta) return script::projectStatusResult(vm, delta.status(), false, false);
|
|
return script::projectResult(vm, self->advanceRuntime(std::move(delta).takeValue()), montageAdvanceValue);
|
|
});
|
|
actionEditor.addFunc("jumpRuntimeSeconds", [vm](ScriptActionTimelineEditor* self, float targetSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto target = seconds(targetSeconds);
|
|
if (!target) return script::projectStatusResult(vm, target.status(), false, false);
|
|
return script::projectResult(vm, self->jumpRuntime(std::move(target).takeValue()), montageAdvanceValue);
|
|
});
|
|
actionEditor.addFunc("jumpRuntimeSection", [vm](ScriptActionTimelineEditor* self, int sectionIndex) {
|
|
if (!self || sectionIndex < 0)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "runtime section index is invalid", "index");
|
|
return script::projectResult(vm, self->jumpRuntimeSection(static_cast<std::size_t>(sectionIndex)),
|
|
montageAdvanceValue);
|
|
});
|
|
actionEditor.addFunc(
|
|
"syncRuntimeSection", [vm](ScriptActionTimelineEditor* self, int sectionIndex, float targetSeconds) {
|
|
if (!self || sectionIndex < 0)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "runtime section index is invalid", "index");
|
|
auto target = seconds(targetSeconds);
|
|
if (!target) return script::projectStatusResult(vm, target.status(), false, false);
|
|
return script::projectResult(
|
|
vm, self->syncRuntimeSection(static_cast<std::size_t>(sectionIndex), std::move(target).takeValue()));
|
|
});
|
|
actionEditor.addFunc("beginRuntimeBlendOut", [vm](ScriptActionTimelineEditor* self, float durationSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto duration = seconds(durationSeconds);
|
|
if (!duration) return script::projectStatusResult(vm, duration.status(), false, false);
|
|
return script::projectResult(vm, self->beginRuntimeBlendOut(std::move(duration).takeValue()),
|
|
montageAdvanceValue);
|
|
});
|
|
actionEditor.addFunc("setRuntimePaused", [](ScriptActionTimelineEditor* self, bool paused) {
|
|
if (self) self->setRuntimePaused(paused);
|
|
});
|
|
actionEditor.addFunc("isRuntimePaused",
|
|
[](ScriptActionTimelineEditor* self) { return self && self->runtimePaused(); });
|
|
actionEditor.addFunc("cancelRuntime", [vm](ScriptActionTimelineEditor* self) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
return script::projectResult(vm, self->cancelRuntime());
|
|
});
|
|
actionEditor.addFunc("getRuntimePose",
|
|
[](ScriptActionTimelineEditor* self) { return self ? self->runtimePose() : nullptr; });
|
|
actionEditor.addFunc("isRuntimePlaying",
|
|
[](ScriptActionTimelineEditor* self) { return self && self->runtimePlaying(); });
|
|
actionEditor.addFunc("getRuntimeSectionId", [](ScriptActionTimelineEditor* self) {
|
|
if (!self || !self->runtimeAdvance() || !self->runtimeAdvance()->sectionId) return std::string{};
|
|
return self->runtimeAdvance()->sectionId->format();
|
|
});
|
|
actionEditor.addFunc("getRuntimeRootMotionX", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->runtimeAdvance() ? self->runtimeAdvance()->rootMotion.px : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRuntimeRootMotionY", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->runtimeAdvance() ? self->runtimeAdvance()->rootMotion.py : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRuntimeRootMotionZ", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->runtimeAdvance() ? self->runtimeAdvance()->rootMotion.pz : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRuntimeWeight", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->runtimeAdvance() ? static_cast<float>(self->runtimeAdvance()->weight) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRuntimeEventCount", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->runtimeAdvance() ? static_cast<int>(self->runtimeAdvance()->events.size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getRuntimeEventKind", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self || !self->runtimeAdvance() || index < 0 ||
|
|
static_cast<std::size_t>(index) >= self->runtimeAdvance()->events.size())
|
|
return std::string{};
|
|
return eventKind(self->runtimeAdvance()->events[static_cast<std::size_t>(index)].kind);
|
|
});
|
|
actionEditor.addFunc("getRuntimeEventType", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self || !self->runtimeAdvance() || index < 0 ||
|
|
static_cast<std::size_t>(index) >= self->runtimeAdvance()->events.size())
|
|
return std::string{};
|
|
return self->runtimeAdvance()->events[static_cast<std::size_t>(index)].type.format();
|
|
});
|
|
actionEditor.addFunc("getRuntimeEventSeconds", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self || !self->runtimeAdvance() || index < 0 ||
|
|
static_cast<std::size_t>(index) >= self->runtimeAdvance()->events.size())
|
|
return 0.0f;
|
|
return static_cast<float>(self->runtimeAdvance()->events[static_cast<std::size_t>(index)].time.seconds());
|
|
});
|
|
actionEditor.addFunc("getRuntimeActiveBlockCount", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->runtimeAdvance() ? static_cast<int>(self->runtimeAdvance()->activeBlocks.size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getRuntimeActiveBlockType", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self || !self->runtimeAdvance() || index < 0 ||
|
|
static_cast<std::size_t>(index) >= self->runtimeAdvance()->activeBlocks.size())
|
|
return std::string{};
|
|
return self->runtimeAdvance()->activeBlocks[static_cast<std::size_t>(index)].type.format();
|
|
});
|
|
actionEditor.addFunc("getRuntimeActiveBlockLocalSeconds", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self || !self->runtimeAdvance() || index < 0 ||
|
|
static_cast<std::size_t>(index) >= self->runtimeAdvance()->activeBlocks.size())
|
|
return 0.0f;
|
|
return static_cast<float>(
|
|
self->runtimeAdvance()->activeBlocks[static_cast<std::size_t>(index)].localTime.seconds());
|
|
});
|
|
actionEditor.addFunc("getRuntimePhysicalSection", [](ScriptActionTimelineEditor* self) {
|
|
if (!self) return -1;
|
|
auto result = self->runtimePhysicalSection();
|
|
return result ? static_cast<int>(result.value()) : -1;
|
|
});
|
|
actionEditor.addFunc("getRuntimeSectionProgress", [](ScriptActionTimelineEditor* self, int sectionIndex) {
|
|
if (!self || sectionIndex < 0) return 0.0f;
|
|
auto result = self->runtimeSectionProgress(static_cast<std::size_t>(sectionIndex));
|
|
return result ? static_cast<float>(result.value()) : 0.0f;
|
|
});
|
|
|
|
actionEditor.addFunc("play", [](ScriptActionTimelineEditor* self) {
|
|
if (self) self->editor().play();
|
|
});
|
|
actionEditor.addFunc("pause", [](ScriptActionTimelineEditor* self) {
|
|
if (self) self->editor().pause();
|
|
});
|
|
actionEditor.addFunc("isPlaying",
|
|
[](ScriptActionTimelineEditor* self) { return self && self->editor().playing(); });
|
|
actionEditor.addFunc("canUndo", [](ScriptActionTimelineEditor* self) { return self && self->editor().canUndo(); });
|
|
actionEditor.addFunc("canRedo", [](ScriptActionTimelineEditor* self) { return self && self->editor().canRedo(); });
|
|
actionEditor.addFunc("isDragging", [](ScriptActionTimelineEditor* self) {
|
|
return self && self->widget().dragStatus() == TimelineDragStatus::Active;
|
|
});
|
|
actionEditor.addFunc("getDuration", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<float>(self->editor().target().timeline().duration.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getPreviewTime", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<float>(self->editor().previewTime().seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRevision", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<std::int64_t>(self->editor().target().revision()) : std::int64_t{0};
|
|
});
|
|
actionEditor.addFunc("getAnimationUri", [](ScriptActionTimelineEditor* self) {
|
|
return self ? self->editor().target().timeline().animationUri : std::string{};
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionCount", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<int>(self->editor().target().timeline().animationSections.size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionId", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? section->id.format() : std::string{};
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionUri", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? section->animationUri : std::string{};
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionStart", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? static_cast<float>(section->start.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionEnd", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? static_cast<float>(section->end.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionBlendIn", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? static_cast<float>(section->blendIn.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionSourceStart", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? static_cast<float>(section->sourceStart.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionSourceEnd", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? static_cast<float>(section->sourceEnd.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getAnimationSectionBlendCurve", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* section = self ? sectionAt(self->editor().target().timeline(), index) : nullptr;
|
|
return section ? std::string(action::actionBlendCurveName(section->blendCurve)) : std::string{};
|
|
});
|
|
actionEditor.addFunc("addAnimationSection", [vm](ScriptActionTimelineEditor* self, const std::string& sectionId,
|
|
const std::string& animationUri, float startSeconds,
|
|
float endSeconds, float blendInSeconds) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto parsed = LogicalId::parse(sectionId);
|
|
if (!parsed)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "animation section id is not canonical",
|
|
"sectionId");
|
|
auto start = seconds(startSeconds);
|
|
auto end = seconds(endSeconds);
|
|
auto blend = seconds(blendInSeconds);
|
|
if (!start) return script::projectStatusResult(vm, start.status(), false, false);
|
|
if (!end) return script::projectStatusResult(vm, end.status(), false, false);
|
|
if (!blend) return script::projectStatusResult(vm, blend.status(), false, false);
|
|
action::ActionAnimationSection section{*parsed, animationUri, start.value(), end.value(), blend.value()};
|
|
return project(vm, self->editor().addAnimationSection(std::move(section)));
|
|
});
|
|
actionEditor.addFunc("editAnimationSection", [vm](ScriptActionTimelineEditor* self, const std::string& sectionId,
|
|
float startSeconds, float endSeconds, float blendInSeconds,
|
|
const std::string& animationUri) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto parsed = LogicalId::parse(sectionId);
|
|
if (!parsed)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "animation section id is not canonical",
|
|
"sectionId");
|
|
auto start = seconds(startSeconds);
|
|
auto end = seconds(endSeconds);
|
|
auto blend = seconds(blendInSeconds);
|
|
if (!start) return script::projectStatusResult(vm, start.status(), false, false);
|
|
if (!end) return script::projectStatusResult(vm, end.status(), false, false);
|
|
if (!blend) return script::projectStatusResult(vm, blend.status(), false, false);
|
|
return project(
|
|
vm, self->editor().editAnimationSection(*parsed, start.value(), end.value(), blend.value(), animationUri));
|
|
});
|
|
actionEditor.addFunc(
|
|
"removeAnimationSection", [vm](ScriptActionTimelineEditor* self, const std::string& sectionId) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto parsed = LogicalId::parse(sectionId);
|
|
if (!parsed)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "animation section id is not canonical",
|
|
"sectionId");
|
|
return project(vm, self->editor().removeAnimationSection(*parsed));
|
|
});
|
|
actionEditor.addFunc("editAnimationSectionSource", [vm](ScriptActionTimelineEditor* self,
|
|
const std::string& sectionId, float sourceStartSeconds,
|
|
float sourceEndSeconds, const std::string& blendCurve) {
|
|
if (!self)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline editor must not be null");
|
|
auto parsed = LogicalId::parse(sectionId);
|
|
auto curve = action::actionBlendCurveFromName(blendCurve);
|
|
if (!parsed || !curve)
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "section id or blend curve is invalid",
|
|
"sectionSource");
|
|
auto sourceStart = seconds(sourceStartSeconds);
|
|
auto sourceEnd = seconds(sourceEndSeconds);
|
|
if (!sourceStart) return script::projectStatusResult(vm, sourceStart.status(), false, false);
|
|
if (!sourceEnd) return script::projectStatusResult(vm, sourceEnd.status(), false, false);
|
|
return project(
|
|
vm, self->editor().editAnimationSectionSource(*parsed, sourceStart.value(), sourceEnd.value(), *curve));
|
|
});
|
|
actionEditor.addFunc("getTrackCount", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<int>(self->editor().target().timeline().tracks.size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getTrackId", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* track = self ? trackAt(self->editor().target().timeline(), index) : nullptr;
|
|
return track ? track->id.format() : std::string{};
|
|
});
|
|
actionEditor.addFunc("getTrackLabel", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* track = self ? trackAt(self->editor().target().timeline(), index) : nullptr;
|
|
return track ? track->label : std::string{};
|
|
});
|
|
actionEditor.addFunc("getTrackKind", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* track = self ? trackAt(self->editor().target().timeline(), index) : nullptr;
|
|
return track ? std::string(action::actionTrackKindName(track->kind)) : std::string{};
|
|
});
|
|
actionEditor.addFunc("getTrackMuted", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto* track = self ? trackAt(self->editor().target().timeline(), index) : nullptr;
|
|
return track && track->muted;
|
|
});
|
|
|
|
actionEditor.addFunc("getLayoutWidth",
|
|
[](ScriptActionTimelineEditor* self) { return self ? self->widget().layout().width : 0.0f; });
|
|
actionEditor.addFunc("getLayoutHeight",
|
|
[](ScriptActionTimelineEditor* self) { return self ? self->widget().layout().height : 0.0f; });
|
|
actionEditor.addFunc("getPlayheadX", [](ScriptActionTimelineEditor* self) {
|
|
return self ? self->widget().layout().playheadX : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRulerTickCount", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<int>(self->widget().layout().rulerTicks.size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getRulerTickX", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
return index >= 0 && static_cast<std::size_t>(index) < layout.rulerTicks.size()
|
|
? layout.rulerTicks[static_cast<std::size_t>(index)].x
|
|
: 0.0f;
|
|
});
|
|
actionEditor.addFunc("getRulerTickMajor", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
return index >= 0 && static_cast<std::size_t>(index) < layout.rulerTicks.size() &&
|
|
layout.rulerTicks[static_cast<std::size_t>(index)].major;
|
|
});
|
|
actionEditor.addFunc("getItemCount", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<int>(self->widget().layout().items.size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getItemId", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item ? item->itemId.format() : std::string{};
|
|
});
|
|
actionEditor.addFunc("getItemType", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item ? item->type.format() : std::string{};
|
|
});
|
|
actionEditor.addFunc("getItemState", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item && item->state;
|
|
});
|
|
actionEditor.addFunc("getItemSelected", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item && item->selected;
|
|
});
|
|
actionEditor.addFunc("getItemMinX", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item ? item->minimumX : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getItemMaxX", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item ? item->maximumX : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getItemMinY", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item ? item->minimumY : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getItemMaxY", [](ScriptActionTimelineEditor* self, int index) {
|
|
const auto layout = self ? self->widget().layout() : TimelineWidgetLayout{};
|
|
const auto* item = itemAt(layout, index);
|
|
return item ? item->maximumY : 0.0f;
|
|
});
|
|
|
|
actionEditor.addFunc("getStateStart", [](ScriptActionTimelineEditor* self, const std::string& itemId) {
|
|
const auto* state = self ? findState(self->editor().target().timeline(), itemId) : nullptr;
|
|
return state ? static_cast<float>(state->start.seconds()) : 0.0f;
|
|
});
|
|
actionEditor.addFunc("getStateEnd", [](ScriptActionTimelineEditor* self, const std::string& itemId) {
|
|
const auto* state = self ? findState(self->editor().target().timeline(), itemId) : nullptr;
|
|
return state ? static_cast<float>(state->end.seconds()) : 0.0f;
|
|
});
|
|
|
|
actionEditor.addFunc("getEventCount", [](ScriptActionTimelineEditor* self) {
|
|
return self ? static_cast<int>(self->editor().previewEvents().size()) : 0;
|
|
});
|
|
actionEditor.addFunc("getEventItemId", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self) return std::string{};
|
|
const auto& events = self->editor().previewEvents();
|
|
return index >= 0 && static_cast<std::size_t>(index) < events.size()
|
|
? events[static_cast<std::size_t>(index)].itemId.format()
|
|
: std::string{};
|
|
});
|
|
actionEditor.addFunc("getEventType", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self) return std::string{};
|
|
const auto& events = self->editor().previewEvents();
|
|
return index >= 0 && static_cast<std::size_t>(index) < events.size()
|
|
? events[static_cast<std::size_t>(index)].type.format()
|
|
: std::string{};
|
|
});
|
|
actionEditor.addFunc("getEventTime", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self) return 0.0f;
|
|
const auto& events = self->editor().previewEvents();
|
|
return index >= 0 && static_cast<std::size_t>(index) < events.size()
|
|
? static_cast<float>(events[static_cast<std::size_t>(index)].time.seconds())
|
|
: 0.0f;
|
|
});
|
|
actionEditor.addFunc("getEventKind", [](ScriptActionTimelineEditor* self, int index) {
|
|
if (!self) return std::string{};
|
|
const auto& events = self->editor().previewEvents();
|
|
return index >= 0 && static_cast<std::size_t>(index) < events.size()
|
|
? eventKind(events[static_cast<std::size_t>(index)].kind)
|
|
: std::string{};
|
|
});
|
|
|
|
moduleClass.addFunc("create", [vm](eve::action_editor::ActionEditorModule*, const std::string& targetId,
|
|
const ssq::Object& timelineObject) {
|
|
if (targetId.empty())
|
|
return bindingFailure(vm, DiagnosticCode::InvalidArgument, "action timeline target id must not be empty",
|
|
"targetId");
|
|
script::SquirrelValueOptions options;
|
|
options.source = kBindingSource;
|
|
auto value = script::valueFromSquirrel(timelineObject, options);
|
|
if (!value) return script::projectStatusResult(vm, value.status(), false, false);
|
|
auto timeline = action::ActionTimeline::fromValue(value.value());
|
|
if (!timeline) return script::projectStatusResult(vm, timeline.status(), false, false);
|
|
auto registry = action::ActionNotifyRegistry::withBuiltins();
|
|
if (!registry) return script::projectStatusResult(vm, registry.status(), false, false);
|
|
auto object = script::makeOwnedSquirrelInstance<ScriptActionTimelineEditor>(
|
|
vm, std::make_unique<ScriptActionTimelineEditor>(targetId, std::move(timeline).takeValue(),
|
|
std::move(registry).takeValue()));
|
|
if (!object) return script::projectStatusResult(vm, object.status(), false, false);
|
|
ssq::Object owned = std::move(object).takeValue();
|
|
auto result = script::projectStatusResult(vm, Status::success(StatusCode::Applied), true, false);
|
|
result.set("value", owned);
|
|
result.set("ownership", std::string("owned"));
|
|
return result;
|
|
});
|
|
}
|
|
|
|
} // namespace eve::editor
|