add animation ticker inside render loop
This commit is contained in:
@@ -102,6 +102,7 @@ static void* freeResourceGlobal(void* mem, size_t size, void* misc) {
|
|||||||
*(framesArr+i) = [[frameData objectAtIndex:i] floatValue];
|
*(framesArr+i) = [[frameData objectAtIndex:i] floatValue];
|
||||||
}
|
}
|
||||||
_viewer->animateWeights((float*)framesArr, [numWeights intValue], [frameData count], [frameRate floatValue]);
|
_viewer->animateWeights((float*)framesArr, [numWeights intValue], [frameData count], [frameRate floatValue]);
|
||||||
|
result(@"OK");
|
||||||
} else if([@"createMorpher" isEqualToString:call.method]) {
|
} else if([@"createMorpher" isEqualToString:call.method]) {
|
||||||
const char* meshName = [call.arguments[0] UTF8String];
|
const char* meshName = [call.arguments[0] UTF8String];
|
||||||
NSArray* primitiveIndices = call.arguments[1];
|
NSArray* primitiveIndices = call.arguments[1];
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ using namespace filament;
|
|||||||
using namespace filament::math;
|
using namespace filament::math;
|
||||||
using namespace gltfio;
|
using namespace gltfio;
|
||||||
using namespace utils;
|
using namespace utils;
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
|
||||||
namespace filament {
|
namespace filament {
|
||||||
class IndirectLight;
|
class IndirectLight;
|
||||||
@@ -148,13 +150,19 @@ FilamentViewer::FilamentViewer(
|
|||||||
manipulator =
|
manipulator =
|
||||||
Manipulator<float>::Builder().orbitHomePosition(0.0f, 0.0f, 0.0f).targetPosition(0.0f, 0.0f, 0).build(Mode::ORBIT);
|
Manipulator<float>::Builder().orbitHomePosition(0.0f, 0.0f, 0.0f).targetPosition(0.0f, 0.0f, 0).build(Mode::ORBIT);
|
||||||
_asset = nullptr;
|
_asset = nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FilamentViewer::~FilamentViewer() {
|
FilamentViewer::~FilamentViewer() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printWeights(float* weights, int numWeights) {
|
||||||
|
for(int i =0; i < numWeights; i++) {
|
||||||
|
// std::cout << weights[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FilamentViewer::loadResources(string relativeResourcePath) {
|
void FilamentViewer::loadResources(string relativeResourcePath) {
|
||||||
const char* const* const resourceUris = _asset->getResourceUris();
|
const char* const* const resourceUris = _asset->getResourceUris();
|
||||||
const size_t resourceUriCount = _asset->getResourceUriCount();
|
const size_t resourceUriCount = _asset->getResourceUriCount();
|
||||||
@@ -186,47 +194,10 @@ void FilamentViewer::releaseSourceAssets() {
|
|||||||
_freeResource((void*)materialProviderResources.data, materialProviderResources.size, nullptr);
|
_freeResource((void*)materialProviderResources.data, materialProviderResources.size, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FilamentViewer::animateWeightsInternal(float* data, int numWeights, int length, float frameRate) {
|
|
||||||
int frameIndex = 0;
|
|
||||||
int numFrames = length / numWeights;
|
|
||||||
float frameLength = 1000 / frameRate;
|
|
||||||
|
|
||||||
applyWeights(data, numWeights);
|
|
||||||
auto animationStartTime = std::chrono::high_resolution_clock::now();
|
|
||||||
while(frameIndex < numFrames) {
|
|
||||||
duration dur = std::chrono::high_resolution_clock::now() - animationStartTime;
|
|
||||||
int msElapsed = dur.count();
|
|
||||||
if(msElapsed > frameLength) {
|
|
||||||
std::cout << "frame" << frameIndex << std::endl;
|
|
||||||
frameIndex++;
|
|
||||||
applyWeights(data + (frameIndex * numWeights), numWeights);
|
|
||||||
animationStartTime = std::chrono::high_resolution_clock::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void FilamentViewer::animateWeights(float* data, int numWeights, int length, float frameRate) {
|
void FilamentViewer::animateWeights(float* data, int numWeights, int length, float frameRate) {
|
||||||
int numFrames = length / numWeights;
|
transformToUnitCube();
|
||||||
float frameLength = 1000 / frameRate;
|
morphAnimationBuffer = std::make_unique<MorphAnimationBuffer>(data, numWeights, length / numWeights, 1000 / frameRate );
|
||||||
|
|
||||||
thread* t = new thread(
|
|
||||||
[=](){
|
|
||||||
int frameIndex = 0;
|
|
||||||
|
|
||||||
applyWeights(data, numWeights);
|
|
||||||
auto animationStartTime = std::chrono::high_resolution_clock::now();
|
|
||||||
while(frameIndex < numFrames) {
|
|
||||||
duration dur = std::chrono::high_resolution_clock::now() - animationStartTime;
|
|
||||||
int msElapsed = dur.count();
|
|
||||||
if(msElapsed > frameLength) {
|
|
||||||
frameIndex++;
|
|
||||||
applyWeights(data + (frameIndex * numWeights), numWeights);
|
|
||||||
animationStartTime = std::chrono::high_resolution_clock::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FilamentViewer::loadGltf(const char* const uri, const char* const relativeResourcePath) {
|
void FilamentViewer::loadGltf(const char* const uri, const char* const relativeResourcePath) {
|
||||||
@@ -254,8 +225,6 @@ void FilamentViewer::loadGltf(const char* const uri, const char* const relativeR
|
|||||||
|
|
||||||
transformToUnitCube();
|
transformToUnitCube();
|
||||||
|
|
||||||
startTime = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringList FilamentViewer::getTargetNames(const char* meshName) {
|
StringList FilamentViewer::getTargetNames(const char* meshName) {
|
||||||
@@ -307,9 +276,8 @@ void FilamentViewer::animateBones() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FilamentViewer::playAnimation(int index) {
|
void FilamentViewer::playAnimation(int index) {
|
||||||
_activeAnimation = index;
|
embeddedAnimationBuffer = make_unique<EmbeddedAnimationBuffer>(index, _animator->getAnimationDuration(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FilamentViewer::loadSkybox(const char* const skyboxPath, const char* const iblPath) {
|
void FilamentViewer::loadSkybox(const char* const skyboxPath, const char* const iblPath) {
|
||||||
ResourceBuffer skyboxBuffer = _loadResource(skyboxPath);
|
ResourceBuffer skyboxBuffer = _loadResource(skyboxPath);
|
||||||
@@ -380,23 +348,106 @@ void FilamentViewer::render() {
|
|||||||
manipulator->getLookAt(&eye, &target, &upward);
|
manipulator->getLookAt(&eye, &target, &upward);
|
||||||
|
|
||||||
_mainCamera->lookAt(eye, target, upward);
|
_mainCamera->lookAt(eye, target, upward);
|
||||||
|
|
||||||
if(_animator) {
|
if(morphAnimationBuffer) {
|
||||||
|
updateMorphAnimation();
|
||||||
duration dur = std::chrono::high_resolution_clock::now() - startTime;
|
}
|
||||||
if (_activeAnimation >= 0 && _animator->getAnimationCount() > 0) {
|
|
||||||
_animator->applyAnimation(_activeAnimation, dur.count() / 1000);
|
if(embeddedAnimationBuffer) {
|
||||||
_animator->updateBoneMatrices();
|
updateEmbeddedAnimation();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the scene, unless the renderer wants to skip the frame.
|
// Render the scene, unless the renderer wants to skip the frame.
|
||||||
if (_renderer->beginFrame(_swapChain)) {
|
if (_renderer->beginFrame(_swapChain)) {
|
||||||
_renderer->render(_view);
|
_renderer->render(_view);
|
||||||
_renderer->endFrame();
|
_renderer->endFrame();
|
||||||
|
} else {
|
||||||
|
std::cout << "Skipping frame" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//void FilamentViewer::updateAnimation(AnimationBuffer animation, std::function<void(int)> moo) {
|
||||||
|
// if(morphAnimationBuffer.frameIndex >= animation.numFrames) {
|
||||||
|
// this.animation = null;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if(animation.frameIndex == -1) {
|
||||||
|
// animation->frameIndex++;
|
||||||
|
// animation->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
// callback(); // applyWeights(morphAnimationBuffer->frameData, morphAnimationBuffer->numWeights);
|
||||||
|
// } else {
|
||||||
|
// duration dur = std::chrono::high_resolution_clock::now() - morphAnimationBuffer->lastTime;
|
||||||
|
// float msElapsed = dur.count();
|
||||||
|
// if(msElapsed > animation->frameLength) {
|
||||||
|
// animation->frameIndex++;
|
||||||
|
// animation->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
// callback(); // applyWeights(frameData + (frameIndex * numWeights), numWeights);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
void FilamentViewer::updateMorphAnimation() {
|
||||||
|
|
||||||
|
if(morphAnimationBuffer->frameIndex >= morphAnimationBuffer->numFrames) {
|
||||||
|
morphAnimationBuffer = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(morphAnimationBuffer->frameIndex == -1) {
|
||||||
|
morphAnimationBuffer->frameIndex++;
|
||||||
|
morphAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
applyWeights(morphAnimationBuffer->frameData, morphAnimationBuffer->numWeights);
|
||||||
|
} else {
|
||||||
|
duration dur = std::chrono::high_resolution_clock::now() - morphAnimationBuffer->lastTime;
|
||||||
|
float microsElapsed = dur.count();
|
||||||
|
if(microsElapsed > (morphAnimationBuffer->frameLength * 1000000)) {
|
||||||
|
morphAnimationBuffer->frameIndex++;
|
||||||
|
morphAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
applyWeights(morphAnimationBuffer->frameData + (morphAnimationBuffer->frameIndex * morphAnimationBuffer->numWeights), morphAnimationBuffer->numWeights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentViewer::updateEmbeddedAnimation() {
|
||||||
|
duration<double> dur = duration_cast<duration<double>>(std::chrono::high_resolution_clock::now() - embeddedAnimationBuffer->lastTime);
|
||||||
|
float startTime = 0;
|
||||||
|
if(!embeddedAnimationBuffer->hasStarted) {
|
||||||
|
embeddedAnimationBuffer->hasStarted = true;
|
||||||
|
embeddedAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
} else if(dur.count() >= embeddedAnimationBuffer->duration) {
|
||||||
|
embeddedAnimationBuffer = nullptr;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
startTime = dur.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_animator->applyAnimation(embeddedAnimationBuffer->animationIndex, startTime);
|
||||||
|
_animator->updateBoneMatrices();
|
||||||
|
|
||||||
|
}
|
||||||
|
//
|
||||||
|
//if(morphAnimationBuffer.frameIndex >= morphAnimationBuffer.numFrames) {
|
||||||
|
// this.morphAnimationBuffer = null;
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//if(morphAnimationBuffer.frameIndex == -1) {
|
||||||
|
// applyWeights(morphAnimationBuffer->frameData, morphAnimationBuffer->numWeights);
|
||||||
|
// morphAnimationBuffer->frameIndex++;
|
||||||
|
// morphAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
//} else {
|
||||||
|
// duration dur = std::chrono::high_resolution_clock::now() - morphAnimationBuffer->lastTime;
|
||||||
|
// float msElapsed = dur.count();
|
||||||
|
// if(msElapsed > morphAnimationBuffer->frameLength) {
|
||||||
|
// frameIndex++;
|
||||||
|
// applyWeights(frameData + (frameIndex * numWeights), numWeights);
|
||||||
|
// morphAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
void FilamentViewer::updateViewportAndCameraProjection(int width, int height, float contentScaleFactor) {
|
void FilamentViewer::updateViewportAndCameraProjection(int width, int height, float contentScaleFactor) {
|
||||||
if (!_view || !_mainCamera || !manipulator) {
|
if (!_view || !_mainCamera || !manipulator) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ using namespace camutils;
|
|||||||
|
|
||||||
namespace mimetic {
|
namespace mimetic {
|
||||||
|
|
||||||
|
typedef std::chrono::time_point<std::chrono::high_resolution_clock> time_point_t;
|
||||||
|
|
||||||
struct StringList {
|
struct StringList {
|
||||||
StringList(const char** strings, const int count) : strings(strings), count(count) {};
|
StringList(const char** strings, const int count) : strings(strings), count(count) {};
|
||||||
const char** strings;
|
const char** strings;
|
||||||
@@ -61,7 +63,31 @@ namespace mimetic {
|
|||||||
uint64_t size;
|
uint64_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::chrono::duration<float, std::milli> duration;
|
struct MorphAnimationBuffer {
|
||||||
|
|
||||||
|
MorphAnimationBuffer(float* frameData,
|
||||||
|
int numWeights,
|
||||||
|
int numFrames,
|
||||||
|
float frameLength) : frameData(frameData), numWeights(numWeights), numFrames(numFrames), frameLength(frameLength) {
|
||||||
|
}
|
||||||
|
|
||||||
|
int frameIndex = -1;
|
||||||
|
int numFrames;
|
||||||
|
float frameLength;
|
||||||
|
time_point_t lastTime;
|
||||||
|
|
||||||
|
float* frameData;
|
||||||
|
int numWeights;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EmbeddedAnimationBuffer {
|
||||||
|
|
||||||
|
EmbeddedAnimationBuffer(int animationIndex, float duration) : animationIndex(animationIndex), duration(duration) {}
|
||||||
|
bool hasStarted = false;
|
||||||
|
int animationIndex;
|
||||||
|
float duration = 0;
|
||||||
|
time_point_t lastTime;
|
||||||
|
};
|
||||||
|
|
||||||
using LoadResource = std::function<ResourceBuffer(const char* uri)>;
|
using LoadResource = std::function<ResourceBuffer(const char* uri)>;
|
||||||
using FreeResource = std::function<void * (void *mem, size_t s, void *)>;
|
using FreeResource = std::function<void * (void *mem, size_t s, void *)>;
|
||||||
@@ -88,7 +114,14 @@ namespace mimetic {
|
|||||||
void loadResources(std::string relativeResourcePath);
|
void loadResources(std::string relativeResourcePath);
|
||||||
void transformToUnitCube();
|
void transformToUnitCube();
|
||||||
void cleanup();
|
void cleanup();
|
||||||
void animateWeightsInternal(float* data, int numWeights, int length, float frameRate);
|
void updateMorphAnimation();
|
||||||
|
void updateEmbeddedAnimation();
|
||||||
|
|
||||||
|
// animation flags;
|
||||||
|
bool isAnimating;
|
||||||
|
unique_ptr<MorphAnimationBuffer> morphAnimationBuffer;
|
||||||
|
unique_ptr<EmbeddedAnimationBuffer> embeddedAnimationBuffer;
|
||||||
|
|
||||||
void* _layer;
|
void* _layer;
|
||||||
|
|
||||||
LoadResource _loadResource;
|
LoadResource _loadResource;
|
||||||
@@ -96,10 +129,6 @@ namespace mimetic {
|
|||||||
|
|
||||||
ResourceBuffer materialProviderResources;
|
ResourceBuffer materialProviderResources;
|
||||||
|
|
||||||
std::chrono::high_resolution_clock::time_point startTime;
|
|
||||||
|
|
||||||
int _activeAnimation = -1;
|
|
||||||
|
|
||||||
Scene* _scene;
|
Scene* _scene;
|
||||||
View* _view;
|
View* _view;
|
||||||
Engine* _engine;
|
Engine* _engine;
|
||||||
@@ -114,7 +143,6 @@ namespace mimetic {
|
|||||||
FilamentAsset* _asset = nullptr;
|
FilamentAsset* _asset = nullptr;
|
||||||
NameComponentManager* _ncm;
|
NameComponentManager* _ncm;
|
||||||
|
|
||||||
|
|
||||||
Entity _sun;
|
Entity _sun;
|
||||||
Texture* _skyboxTexture;
|
Texture* _skyboxTexture;
|
||||||
Skybox* _skybox;
|
Skybox* _skybox;
|
||||||
@@ -131,6 +159,7 @@ namespace mimetic {
|
|||||||
float _cameraFocalLength = 0.0f;
|
float _cameraFocalLength = 0.0f;
|
||||||
|
|
||||||
GPUMorphHelper* morphHelper;
|
GPUMorphHelper* morphHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,12 +57,14 @@ namespace gltfio {
|
|||||||
cgltf_mesh const *mesh = node->mesh;
|
cgltf_mesh const *mesh = node->mesh;
|
||||||
|
|
||||||
if (mesh) {
|
if (mesh) {
|
||||||
std::cout << "Mesh " << mesh->name << " with " << mesh->weights_count << " weights " << std::endl;
|
std::cout << "Mesh " << mesh->name << " with " << mesh->weights_count << " weights and " << mesh->primitives_count << " primitives." << std::endl;
|
||||||
if(strcmp(meshName, mesh->name) == 0) {
|
if(strcmp(meshName, mesh->name) == 0) {
|
||||||
targetMesh = mesh;
|
targetMesh = mesh;
|
||||||
std::cout << "Adding primitive to mesh with " << mesh->primitives_count << " primitives." << std::endl;
|
for(int i = 0; i < numPrimitives; i++) {
|
||||||
for(int i = 0; i < numPrimitives; i++)
|
int primitiveIndex = primitiveIndices[i];
|
||||||
addPrimitive(mesh, primitiveIndices[i]);
|
std::cout << "Adding primitive at index " << primitiveIndex << " to morpher " << std::endl;
|
||||||
|
addPrimitive(mesh, primitiveIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ abstract class FilamentController {
|
|||||||
Future playAnimation(int index);
|
Future playAnimation(int index);
|
||||||
|
|
||||||
// Weights is expected to be a contiguous sequence of floats of size W*F, where W is the number of weights and F is the number of frames
|
// Weights is expected to be a contiguous sequence of floats of size W*F, where W is the number of weights and F is the number of frames
|
||||||
void animate(List<double> weights, int numWeights, double frameRate);
|
Future animate(List<double> weights, int numWeights, double frameRate);
|
||||||
Future createMorpher(String meshName, List<int> primitives);
|
Future createMorpher(String meshName, List<int> primitives);
|
||||||
Future zoom(double z);
|
Future zoom(double z);
|
||||||
}
|
}
|
||||||
@@ -102,8 +102,9 @@ class MimeticFilamentController extends FilamentController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void animate(List<double> weights, int numWeights, double frameRate) async {
|
Future animate(List<double> weights, int numWeights, double frameRate) async {
|
||||||
_channel.invokeMethod("animateWeights", [weights, numWeights, frameRate]);
|
await _channel
|
||||||
|
.invokeMethod("animateWeights", [weights, numWeights, frameRate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future releaseSourceAssets() async {
|
Future releaseSourceAssets() async {
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import 'package:mimetic_filament/view/filament_widget.dart';
|
|||||||
|
|
||||||
class GestureDetectingFilamentView extends StatefulWidget {
|
class GestureDetectingFilamentView extends StatefulWidget {
|
||||||
final FilamentController controller;
|
final FilamentController controller;
|
||||||
|
final bool rotate;
|
||||||
|
|
||||||
const GestureDetectingFilamentView({Key? key, required this.controller})
|
const GestureDetectingFilamentView(
|
||||||
|
{Key? key, required this.controller, this.rotate = false})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -14,7 +16,6 @@ class GestureDetectingFilamentView extends StatefulWidget {
|
|||||||
|
|
||||||
class _GestureDetectingFilamentViewState
|
class _GestureDetectingFilamentViewState
|
||||||
extends State<GestureDetectingFilamentView> {
|
extends State<GestureDetectingFilamentView> {
|
||||||
bool _rotate = false;
|
|
||||||
int _primitiveIndex = 0;
|
int _primitiveIndex = 0;
|
||||||
double _weight = 0.0;
|
double _weight = 0.0;
|
||||||
|
|
||||||
@@ -23,21 +24,23 @@ class _GestureDetectingFilamentViewState
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onPanDown: (details) {
|
onPanDown: (details) {
|
||||||
_rotate
|
widget.rotate
|
||||||
? widget.controller.rotateStart(
|
? widget.controller.rotateStart(
|
||||||
details.localPosition.dx, details.localPosition.dy)
|
details.localPosition.dx, details.localPosition.dy)
|
||||||
: widget.controller
|
: widget.controller
|
||||||
.panStart(details.localPosition.dx, details.localPosition.dy);
|
.panStart(details.localPosition.dx, details.localPosition.dy);
|
||||||
},
|
},
|
||||||
onPanUpdate: (details) {
|
onPanUpdate: (details) {
|
||||||
_rotate
|
widget.rotate
|
||||||
? widget.controller.rotateUpdate(
|
? widget.controller.rotateUpdate(
|
||||||
details.localPosition.dx, details.localPosition.dy)
|
details.localPosition.dx, details.localPosition.dy)
|
||||||
: widget.controller.panUpdate(
|
: widget.controller.panUpdate(
|
||||||
details.localPosition.dx, details.localPosition.dy);
|
details.localPosition.dx, details.localPosition.dy);
|
||||||
},
|
},
|
||||||
onPanEnd: (d) {
|
onPanEnd: (d) {
|
||||||
_rotate ? widget.controller.rotateEnd() : widget.controller.panEnd();
|
widget.rotate
|
||||||
|
? widget.controller.rotateEnd()
|
||||||
|
: widget.controller.panEnd();
|
||||||
},
|
},
|
||||||
child: FilamentWidget(controller: widget.controller));
|
child: FilamentWidget(controller: widget.controller));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user