/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "pch.h" #include "../EditorCommon/Timeline.h" #include "../EditorCommon/TimelineContent.h" #include "PlaybackPanel.h" #include "CharacterDocument.h" #include <QPushButton> #include <QLineEdit> #include <QSpinBox> #include <QBoxLayout> #include <QComboBox> #include <QLabel> #include <QToolButton> #include <QMenu> #include "EntryList.h" #include "AnimationList.h" #include "Serialization.h" #include "Expected.h" #include "AnimEventPresetPanel.h" #include "CharacterToolSystem.h" #include "CharacterGizmoManager.h" namespace CharacterTool { SERIALIZATION_ENUM_BEGIN(ETimeUnits, "Time Units") SERIALIZATION_ENUM(TIME_IN_SECONDS, "seconds", "Seconds"); SERIALIZATION_ENUM(TIME_IN_FRAMES, "frames", "Frames"); SERIALIZATION_ENUM(TIME_NORMALIZED, "normalized", "Normalized"); SERIALIZATION_ENUM_END() static SEntry<AnimationContent>* GetActiveAnimationEntry(System * system) { ExplorerEntry* activeAnimationEntry = system->document->GetActiveAnimationEntry(); if (!activeAnimationEntry) { return 0; } return system->animationList->GetEntry(activeAnimationEntry->id); } static float gPlaybackSpeeds[] = { 0.01f, 0.1f, 0.25f, 0.50f, 1.0f, 1.5f, 2.0f, 10.0f }; PlaybackPanel::PlaybackPanel(QWidget* parent, System* system, AnimEventPresetPanel* presetPanel) : QWidget(parent) , m_playing(false) , m_system(system) , m_normalizedTime(0.0f) , m_duration(1.0f) , m_frameRate(DEFAULT_FRAME_RATE) , m_timeEditChanging(false) , m_timeUnits(TIME_IN_SECONDS) , m_timelineContent(new STimelineContent()) , m_presetPanel(presetPanel) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); EXPECTED(connect(m_system->document.get(), SIGNAL(SignalPlaybackTimeChanged()), SLOT(OnPlaybackTimeChanged()))); EXPECTED(connect(m_system->document.get(), SIGNAL(SignalPlaybackStateChanged()), SLOT(OnPlaybackStateChanged()))); EXPECTED(connect(m_system->document.get(), SIGNAL(SignalPlaybackOptionsChanged()), SLOT(OnPlaybackOptionsChanged()))); EXPECTED(connect(m_system->document.get(), SIGNAL(SignalActiveAnimationSwitched()), SLOT(OnDocumentActiveAnimationSwitched()))); EXPECTED(connect(m_system->explorer.get(), SIGNAL(SignalEntryModified(ExplorerEntryModifyEvent &)), SLOT(OnExplorerEntryModified(ExplorerEntryModifyEvent &)))); EXPECTED(connect(m_system->characterGizmoManager.get(), SIGNAL(SignalSubselectionChanged(int)), SLOT(OnSubselectionChanged(int)))); EXPECTED(connect(presetPanel, SIGNAL(SignalPutEvent(const AnimEventPreset&)), this, SLOT(PutAnimEvent(const AnimEventPreset&)))); EXPECTED(connect(presetPanel, SIGNAL(SignalPresetsChanged()), this, SLOT(OnPresetsChanged()))); QBoxLayout* topHBox = new QBoxLayout(QBoxLayout::LeftToRight); { { QBoxLayout* vbox = new QBoxLayout(QBoxLayout::TopToBottom); vbox->setSpacing(2); topHBox->addLayout(vbox); { QBoxLayout* hbox = new QBoxLayout(QBoxLayout::LeftToRight); hbox->addWidget(m_timeEdit = new QDoubleSpinBox(), 1); EXPECTED(connect(m_timeEdit, SIGNAL(valueChanged(double)), this, SLOT(OnTimeEditValueChanged(double)))); EXPECTED(connect(m_timeEdit, SIGNAL(editingFinished()), this, SLOT(OnTimeEditEditingFinished()))); m_activeControls.push_back(m_timeEdit); hbox->addWidget(m_timeTotalLabel = new QLabel(), 0); m_timeTotalLabel->setText(" / 1.0 "); m_activeControls.push_back(m_timeTotalLabel); hbox->addWidget(m_timeUnitsCombo = new QComboBox(), 0); m_timeUnitsCombo->addItem("Seconds"); m_timeUnitsCombo->addItem("Frames"); m_timeUnitsCombo->addItem("Normalized"); m_timeUnitsCombo->setCurrentIndex(0); EXPECTED(connect(m_timeUnitsCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnTimeUnitsChanged(int)))); vbox->addLayout(hbox); m_activeControls.push_back(m_timeUnitsCombo); hbox->addWidget(m_speedCombo = new QComboBox(), 0); for (size_t i = 0; i < sizeof(gPlaybackSpeeds) / sizeof(gPlaybackSpeeds[0]); ++i) { float speed = gPlaybackSpeeds[i]; QString str; str.asprintf("%.2fx", speed); m_speedCombo->addItem(str); } EXPECTED(connect(m_speedCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSpeedChanged(int)))); m_activeControls.push_back(m_speedCombo); } { QBoxLayout* hbox = new QBoxLayout(QBoxLayout::LeftToRight); hbox->setSpacing(4); vbox->addLayout(hbox); hbox->addWidget(m_playPauseButton = new QPushButton("Play"), 1); connect(m_playPauseButton, SIGNAL(clicked()), this, SLOT(OnPlayPausePushed())); m_playPauseButton->setIcon(m_playIcon); m_playPauseButton->setEnabled(false); m_activeControls.push_back(m_playPauseButton); m_loopButton = new QPushButton("Loop", this); m_loopButton->setCheckable(true); m_loopButton->setChecked(m_system->document->GetPlaybackOptions().loopAnimation); connect(m_loopButton, SIGNAL(toggled(bool)), this, SLOT(OnLoopToggled(bool))); hbox->addWidget(m_loopButton, 0); m_activeControls.push_back(m_loopButton); m_optionsButton = new QPushButton(QIcon("Editor/Icons/animation/playback_options.png"), QString(), this); { QMenu* optionsMenu = new QMenu(this); m_optionsButton->setMenu(optionsMenu); m_actionPlayOnSelection = optionsMenu->addAction("Play when animation selected", this, SLOT(OnOptionPlayOnSelection())); m_actionPlayOnSelection->setCheckable(true); m_actionPlayFromTheStart = optionsMenu->addAction("Always play from the begining", this, SLOT(OnOptionPlayFromTheStart())); m_actionPlayFromTheStart->setCheckable(true); m_actionFirstFrameAtEnd = optionsMenu->addAction("First frame at the end of timeline", this, SLOT(OnOptionFirstFrameAtEnd())); m_actionFirstFrameAtEnd->setCheckable(true); m_actionWrapSlider = optionsMenu->addAction("Wrap timeline slider", this, SLOT(OnOptionWrapSlider())); m_actionWrapSlider->setCheckable(true); m_actionSmoothTimelineSlider = optionsMenu->addAction("Smooth timeline slider", this, SLOT(OnOptionSmoothTimelineSlider())); m_actionSmoothTimelineSlider->setCheckable(true); } m_activeControls.push_back(m_optionsButton); hbox->addWidget(m_optionsButton, 0); } } m_timeline = new CTimeline(this); m_timeline->SetContent(m_timelineContent.get()); connect(m_timeline, SIGNAL(SignalScrub(bool)), this, SLOT(OnTimelineScrub(bool))); connect(m_timeline, SIGNAL(SignalSelectionChanged(bool)), this, SLOT(OnTimelineSelectionChanged(bool))); connect(m_timeline, SIGNAL(SignalContentChanged(bool)), this, SLOT(OnTimelineChanged(bool))); connect(m_timeline, SIGNAL(SignalPlay()), this, SLOT(OnTimelinePlay())); connect(m_timeline, SIGNAL(SignalNumberHotkey(int)), this, SLOT(OnTimelineHotkey(int))); connect(m_timeline, SIGNAL(SignalUndo()), this, SLOT(OnTimelineUndo())); connect(m_timeline, SIGNAL(SignalRedo()), this, SLOT(OnTimelineRedo())); m_playIcon = QIcon("Editor/Icons/animation/play.png"); m_pauseIcon = QIcon("Editor/Icons/animation/pause.png"); m_timeline->SetSizeToContent(true); topHBox->addWidget(m_timeline, 1); m_activeControls.push_back(m_timeline); setLayout(topHBox); } OnPlaybackOptionsChanged(); OnPlaybackStateChanged(); UpdateTimeUnitsUI(true, true, true); WriteTimeline(); } PlaybackPanel::~PlaybackPanel() { } void PlaybackPanel::OnEventsImport() { } void PlaybackPanel::OnEventsExport() { } void PlaybackPanel::OnTimelineScrub(bool scrubThrough) { float newTime = m_timeline->Time().ToFloat() * m_duration; m_system->document->ScrubTime(newTime, scrubThrough); } void PlaybackPanel::OnTimelineUndo() { ExplorerEntries entries(1, m_system->document->GetActiveAnimationEntry()); if (!entries[0]) { return; } m_system->explorer->Undo(entries, 1); } void PlaybackPanel::OnTimelineRedo() { ExplorerEntries entries(1, m_system->document->GetActiveAnimationEntry()); if (!entries[0]) { return; } m_system->explorer->Redo(entries); } void PlaybackPanel::OnTimelineSelectionChanged(bool continuous) { m_selectedEvents.clear(); STimelineContent& content = *m_timeline->Content(); const STimelineTrack& track = *content.track.tracks[0]; for (size_t i = 0; i < track.elements.size(); ++i) { const STimelineElement& element = track.elements[i]; if (element.selected) { m_selectedEvents.push_back(aznumeric_cast<int>(element.userId - 1)); } } SEntry<AnimationContent>* animation = GetActiveAnimationEntry(m_system); if (!animation) { return; } vector<const void*> handles; handles.reserve(m_selectedEvents.size()); for (size_t i = 0; i < m_selectedEvents.size(); ++i) { size_t index = m_selectedEvents[i]; if (index >= animation->content.events.size()) { continue; } handles.push_back(&animation->content.events[index]); } m_system->characterGizmoManager->SetSubselection(GIZMO_LAYER_ANIMATION, handles); ExplorerEntries selection(1, m_system->document->GetActiveAnimationEntry()); if (selection[0]) { ExplorerEntries currentlySelected; m_system->document->GetSelectedExplorerEntries(¤tlySelected); if (currentlySelected.size() != 1 || currentlySelected[0] != selection[0]) { m_system->document->SetSelectedExplorerEntries(selection, CharacterDocument::SELECT_DO_NOT_REWIND); } } } void PlaybackPanel::OnTimelineChanged(bool continuous) { ReadTimeline(); m_system->explorer->CheckIfModified(m_system->document->GetActiveAnimationEntry(), "Anim Event Change", continuous); } void PlaybackPanel::OnTimelinePlay() { OnPlayPausePushed(); } void PlaybackPanel::OnTimelineHotkey(int number) { const AnimEventPreset* preset = m_presetPanel->GetPresetByHotkey(number); if (!preset) { return; } PutAnimEvent(*preset); } static void SelectElementsById(STimelineTrack* track, const vector<int>& userIds) { for (size_t i = 0; i < track->elements.size(); ++i) { STimelineElement& e = track->elements[i]; bool selected = std::find(userIds.begin(), userIds.end(), e.userId) != userIds.end(); e.selected = selected; } } void PlaybackPanel::OnTimeEditEditingFinished() { if (m_timeEditChanging) { return; } float newTime = float(m_timeEdit->value()); m_system->document->ScrubTime(newTime, false); } void PlaybackPanel::OnTimeEditValueChanged(double newValue) { if (m_timeEditChanging) { return; } double time = 0.0; switch (m_timeUnits) { case TIME_IN_FRAMES: time = newValue / FrameRate(); break; case TIME_NORMALIZED: time = newValue * m_duration; break; case TIME_IN_SECONDS: time = newValue; break; } m_system->document->ScrubTime(float(time), false); } void PlaybackPanel::OnTimeUnitsChanged(int index) { m_timeUnits = (ETimeUnits)index; UpdateTimeUnitsUI(true, true, true); } void PlaybackPanel::OnSpeedChanged(int index) { float speed = 1.0f; if (index >= 0 && index < sizeof(gPlaybackSpeeds) / sizeof(gPlaybackSpeeds[0])) { speed = gPlaybackSpeeds[index]; } m_system->document->GetPlaybackOptions().playbackSpeed = speed; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnPlayPausePushed() { if (m_playing) { m_system->document->Pause(); } else { m_system->document->Play(); } } void PlaybackPanel::OnLoopToggled(bool loop) { m_system->document->GetPlaybackOptions().loopAnimation = loop; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnOptionPlayOnSelection() { m_system->document->GetPlaybackOptions().playOnAnimationSelection = !m_system->document->GetPlaybackOptions().playOnAnimationSelection; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnOptionPlayFromTheStart() { m_system->document->GetPlaybackOptions().playFromTheStart = !m_system->document->GetPlaybackOptions().playFromTheStart; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnOptionWrapSlider() { m_system->document->GetPlaybackOptions().wrapTimelineSlider = !m_system->document->GetPlaybackOptions().wrapTimelineSlider; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnOptionSmoothTimelineSlider() { m_system->document->GetPlaybackOptions().smoothTimelineSlider = !m_system->document->GetPlaybackOptions().smoothTimelineSlider; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnOptionFirstFrameAtEnd() { m_system->document->GetPlaybackOptions().firstFrameAtEndOfTimeline = !m_system->document->GetPlaybackOptions().firstFrameAtEndOfTimeline; m_system->document->PlaybackOptionsChanged(); } void PlaybackPanel::OnPlaybackOptionsChanged() { const PlaybackOptions& options = m_system->document->GetPlaybackOptions(); m_loopButton->setChecked(options.loopAnimation); m_timeEdit->setWrapping(options.loopAnimation); m_timeline->SetCycled(options.loopAnimation && options.wrapTimelineSlider); m_actionPlayOnSelection->setChecked(options.playOnAnimationSelection); m_actionPlayFromTheStart->setChecked(options.playFromTheStart); m_actionWrapSlider->setEnabled(options.loopAnimation); m_actionWrapSlider->setChecked(options.wrapTimelineSlider); m_actionSmoothTimelineSlider->setChecked(options.smoothTimelineSlider); m_actionFirstFrameAtEnd->setChecked(options.firstFrameAtEndOfTimeline); { size_t numSpeeds = sizeof(gPlaybackSpeeds) / sizeof(gPlaybackSpeeds[0]); int selectedSpeedIndex = 0; for (size_t i = 0; i < numSpeeds; ++i) { float speed = gPlaybackSpeeds[i]; if (fabsf(speed - options.playbackSpeed) < 1e-5f) { selectedSpeedIndex = i; } } m_speedCombo->setCurrentIndex(selectedSpeedIndex); } } void PlaybackPanel::OnDocumentActiveAnimationSwitched() { WriteTimeline(); } void PlaybackPanel::OnPlaybackTimeChanged() { float time = m_system->document->PlaybackTime(); float duration = m_system->document->PlaybackDuration(); float frameRate = m_system->document->FrameRate(); float normalizedTime = duration != 0.0f ? time / duration : time; bool timeChanged = false; if (m_normalizedTime != normalizedTime) { m_normalizedTime = normalizedTime; timeChanged = true; } bool durationChanged = false; if (m_duration != duration) { m_duration = duration; durationChanged = true; } bool frameRateChanged = false; if (m_frameRate != frameRate) { m_frameRate = frameRate; frameRateChanged = true; } UpdateTimeUnitsUI(timeChanged, durationChanged, frameRateChanged); } void PlaybackPanel::OnPlaybackStateChanged() { PlaybackState state = m_system->document->GetPlaybackState(); bool controlsEnabled = true; if (state == PLAYBACK_PLAY) { m_playPauseButton->setText("Pause"); controlsEnabled = true; m_playing = true; } else if (state == PLAYBACK_PAUSE) { m_playPauseButton->setText("Play"); controlsEnabled = true; m_playing = false; } else { m_playPauseButton->setText("Play"); controlsEnabled = false; m_playing = false; } const char* tooltip = state == PLAYBACK_UNAVAILABLE ? m_system->document->PlaybackBlockReason() : ""; for (size_t i = 0; i < m_activeControls.size(); ++i) { QWidget* w = m_activeControls[i]; w->setEnabled(controlsEnabled); w->setToolTip(tooltip); } } void PlaybackPanel::OnExplorerEntryModified(ExplorerEntryModifyEvent& ev) { if ((ev.entryParts & ENTRY_PART_CONTENT) == 0) { return; } if (ev.entry == m_system->document->GetActiveAnimationEntry()) { WriteTimeline(); } } float PlaybackPanel::FrameRate() const { return m_frameRate; } void PlaybackPanel::UpdateTimeUnitsUI(bool timeChanged, bool durationChanged, bool frameRateChanged) { float time = m_normalizedTime; float duration = m_duration; int numFrames = int(duration * FrameRate() + 0.5f); if (durationChanged || frameRateChanged) { double minimum = 0.0; double maximum = 1.0; double step = 1.0; double totalDisplay = 0.0; switch (m_timeUnits) { case TIME_IN_SECONDS: minimum = 0.0; maximum = duration; step = 1.0 / double(FrameRate()); totalDisplay = duration; break; case TIME_NORMALIZED: minimum = 0.0; maximum = 1.0; totalDisplay = 1.0; step = 0.01; break; case TIME_IN_FRAMES: minimum = 0.0; maximum = (double)numFrames; totalDisplay = numFrames; step = 1.0; break; } if (duration >= 0.0f) { QString str; if (m_timeUnits == TIME_IN_FRAMES) { str.asprintf("/ %i ", int(totalDisplay + 0.5)); } else { str.asprintf("/ %.2f ", totalDisplay); } m_timeTotalLabel->setText(str); } else { m_timeTotalLabel->setText("/ 0.0 "); } m_timeEditChanging = true; m_timeEdit->setMinimum(minimum); m_timeEdit->setMaximum(maximum); m_timeEdit->setSingleStep(step); m_timeEdit->setDecimals(m_timeUnits == TIME_IN_FRAMES ? 0 : 2); m_timeEditChanging = false; m_timeline->SetTimeUnitScale(aznumeric_cast<float>(totalDisplay), aznumeric_cast<float>(step)); } if (timeChanged) { float normalizedTime = m_normalizedTime; ; if (!m_timeline->IsDragged()) { m_timeline->SetTime(SAnimTime(normalizedTime)); } m_timeline->SetTimeSnapping(m_timeUnits == TIME_IN_FRAMES); if (!m_timeEdit->hasFocus()) { double displayTime = 0.0; switch (m_timeUnits) { case TIME_IN_SECONDS: displayTime = (double)normalizedTime * m_duration; break; case TIME_NORMALIZED: displayTime = normalizedTime; break; case TIME_IN_FRAMES: displayTime = normalizedTime * numFrames; break; } m_timeEditChanging = true; m_timeEdit->setValue(displayTime); m_timeEditChanging = false; } } } void PlaybackPanel::Serialize(Serialization::IArchive& ar) { ar(m_timeUnits, "timeUnits"); if (ar.IsInput()) { UpdateTimeUnitsUI(true, true, true); m_timeUnitsCombo->setCurrentIndex(int(m_timeUnits)); } } void PlaybackPanel::WriteTimeline() { m_timelineContent->track.tracks.resize(1, new STimelineTrack()); STimelineTrack& track = *m_timelineContent->track.tracks[0]; track.startTime = SAnimTime(0.0f); track.endTime = SAnimTime(1.0f); track.height = 24; SEntry<AnimationContent>* animation = GetActiveAnimationEntry(m_system); int numEvents = animation ? animation->content.events.size() : 0; STimelineElements& elements = track.elements; vector<int> selectedIds; { int newSelectedElementIndex = 0; for (size_t i = 0; i < elements.size(); ++i) { if (elements[i].selected) { if (elements[i].userId) { selectedIds.push_back(aznumeric_cast<int>(elements[i].userId)); } else { selectedIds.push_back(numEvents - newSelectedElementIndex); ++newSelectedElementIndex; } } } } elements.clear(); const EventContentToColorMap& eventContentToColor = m_presetPanel->GetEventContentToColorMap(); if (animation) { for (size_t i = 0; i < animation->content.events.size(); ++i) { const AnimEvent& ev = animation->content.events[i]; STimelineElement e; e.start = SAnimTime(ev.startTime); e.userId = i + 1; e.selected = std::find(selectedIds.begin(), selectedIds.end(), e.userId) != selectedIds.end(); AnimEvent eventCopy = ev; eventCopy.startTime = -1.0f; eventCopy.endTime = -1.0f; SerializeToMemory(&e.userSideLoad, Serialization::SStruct(eventCopy)); unsigned int hash = CCrc32::Compute(e.userSideLoad.data(), e.userSideLoad.size()); EventContentToColorMap::const_iterator it = std::lower_bound(eventContentToColor.begin(), eventContentToColor.end(), std::make_pair(hash, ColorB()), [&](const std::pair<unsigned int, ColorB>& a, const std::pair<unsigned int, ColorB>& b) { return a.first < b.first; } ); if (it != eventContentToColor.end() && it->first == hash) { e.color = it->second; } else { e.color = ColorB(255, 255, 255, 255); } elements.push_back(e); } } m_timeline->ContentUpdated(); } void PlaybackPanel::ReadTimeline() { SEntry<AnimationContent>* animation = GetActiveAnimationEntry(m_system); if (!animation) { return; } vector<int> removedElements; if (m_timelineContent->track.tracks.empty() || !m_timelineContent->track.tracks[0]) { return; } const STimelineTrack& track = *m_timelineContent->track.tracks[0]; for (size_t i = 0; i < track.elements.size(); ++i) { const STimelineElement& e = track.elements[i]; size_t index = size_t(e.userId - 1); if (e.added) { AnimEvent newEvent; newEvent.startTime = e.start.ToFloat(); SerializeFromMemory(Serialization::SStruct(newEvent), e.userSideLoad); animation->content.events.push_back(newEvent); index = animation->content.events.size() - 1; } if (e.deleted) { removedElements.push_back(i); } if (index < animation->content.events.size()) { AnimEvent& ev = animation->content.events[index]; if (e.sideLoadChanged) { SerializeFromMemory(Serialization::SStruct(ev), e.userSideLoad); } ev.startTime = e.start.ToFloat(); } } std::sort(removedElements.begin(), removedElements.end()); for (int i = int(removedElements.size() - 1); i >= 0; --i) { animation->content.events.erase(animation->content.events.begin() + removedElements[i]); } } void PlaybackPanel::PutAnimEvent(const AnimEventPreset& preset) { SEntry<AnimationContent>* animation = GetActiveAnimationEntry(m_system); if (!animation) { return; } AnimEvent ev = preset.event; ev.startTime = ev.endTime = m_timeline->Time().ToFloat(); int index = animation->content.events.size(); animation->content.events.push_back(ev); m_system->explorer->CheckIfModified(m_system->document->GetActiveAnimationEntry(), "Add AnimEvent", false); vector<int> selection(1, index + 1); SelectElementsById(m_timelineContent->track.tracks[0], selection); m_timeline->ContentUpdated(); m_timeline->setFocus(); } bool PlaybackPanel::HandleKeyEvent(int key) { return m_timeline->HandleKeyEvent(key); } bool PlaybackPanel::ProcessesKey(const QKeySequence& keySequence) { // We let the timeline handle key events; let it tell us if it's interested in this key sequence as well // If we don't do this, shortcuts may take the key event before it gets here return m_timeline->ProcessesKey(keySequence); } void PlaybackPanel::OnPresetsChanged() { WriteTimeline(); } void PlaybackPanel::OnSubselectionChanged(int layer) { if (layer != GIZMO_LAYER_ANIMATION) { return; } const vector<const void*>& handles = m_system->characterGizmoManager->Subselection(GIZMO_LAYER_ANIMATION); SEntry<AnimationContent>* animation = GetActiveAnimationEntry(m_system); if (!animation) { return; } if (animation->content.events.empty()) { return; } const AnimEvent* events = &animation->content.events[0]; const AnimEvent* eventsEnd = events + animation->content.events.size(); std::vector<int> newSelection; for (size_t i = 0; i < handles.size(); ++i) { const AnimEvent* handle = (const AnimEvent*)handles[i]; if (handle >= events && handle < eventsEnd) { int index = aznumeric_cast<int>(handle - events); int userId = index + 1; newSelection.push_back(userId); } } SelectElementsById(m_timeline->Content()->track.tracks[0].get(), newSelection); m_timeline->ContentUpdated(); } } #include <CharacterTool/PlaybackPanel.moc>