Compare commits
3 Commits
dd9fd10b9b
...
c6026f57dc
Author | SHA1 | Date |
---|---|---|
![]() |
c6026f57dc | 2 months ago |
|
23503592f9 | 2 months ago |
|
a8f3ce3307 | 3 months ago |
@ -0,0 +1,281 @@ |
|||||||
|
// |
||||||
|
// CocosPlayerUtils.swift |
||||||
|
// flutter_cocos_widget |
||||||
|
// |
||||||
|
// Created by Rex Raphael on 30/01/2021. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
|
||||||
|
|
||||||
|
private var cocos_warmed_up = false |
||||||
|
// Hack to work around iOS SDK 4.3 linker problem |
||||||
|
// we need at least one __TEXT, __const section entry in main application .o files |
||||||
|
// to get this section emitted at right time and so avoid LC_ENCRYPTION_INFO size miscalculation |
||||||
|
private let constsection = 0 |
||||||
|
|
||||||
|
// keep arg for cocos init from non main |
||||||
|
var gArgc: Int32 = 0 |
||||||
|
var gArgv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>? = nil |
||||||
|
var appLaunchOpts: [UIApplication.LaunchOptionsKey: Any]? = [:] |
||||||
|
|
||||||
|
/***********************************PLUGIN_ENTRY STARTS**************************************/ |
||||||
|
public func InitCocosIntegration(argc: Int32, argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?) { |
||||||
|
gArgc = argc |
||||||
|
gArgv = argv |
||||||
|
} |
||||||
|
|
||||||
|
public func InitCocosIntegrationWithOptions( |
||||||
|
argc: Int32, |
||||||
|
argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, |
||||||
|
_ launchingOptions: [UIApplication.LaunchOptionsKey: Any]?) { |
||||||
|
gArgc = argc |
||||||
|
gArgv = argv |
||||||
|
appLaunchOpts = launchingOptions |
||||||
|
} |
||||||
|
/***********************************PLUGIN_ENTRY END**************************************/ |
||||||
|
|
||||||
|
// Load cocos framework for fisrt run |
||||||
|
func CocosFrameworkLoad() -> CocosFramework? { |
||||||
|
var bundlePath: String? = nil |
||||||
|
bundlePath = Bundle.main.bundlePath |
||||||
|
bundlePath = (bundlePath ?? "") + "/Frameworks/CocosFramework.framework" |
||||||
|
|
||||||
|
let bundle = Bundle(path: bundlePath ?? "") |
||||||
|
if bundle?.isLoaded == false { |
||||||
|
bundle?.load() |
||||||
|
} |
||||||
|
|
||||||
|
return bundle?.principalClass?.getInstance() |
||||||
|
} |
||||||
|
|
||||||
|
/*********************************** GLOBAL FUNCS & VARS START**************************************/ |
||||||
|
public var globalControllers: Array<FLTCocosWidgetController> = [FLTCocosWidgetController]() |
||||||
|
|
||||||
|
private var cocosPlayerUtils: CocosPlayerUtils? = nil |
||||||
|
func GetCocosPlayerUtils() -> CocosPlayerUtils { |
||||||
|
|
||||||
|
if cocosPlayerUtils == nil { |
||||||
|
cocosPlayerUtils = CocosPlayerUtils() |
||||||
|
} |
||||||
|
|
||||||
|
return cocosPlayerUtils ?? CocosPlayerUtils() |
||||||
|
} |
||||||
|
|
||||||
|
/*********************************** GLOBAL FUNCS & VARS END****************************************/ |
||||||
|
|
||||||
|
var controller: CocosAppController? |
||||||
|
var sharedApplication: UIApplication? |
||||||
|
|
||||||
|
@objc protocol CocosEventListener: AnyObject { |
||||||
|
|
||||||
|
func onReceiveMessage(_ message: UnsafePointer<Int8>?) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@objc public class CocosPlayerUtils: UIResponder, UIApplicationDelegate, CocosFrameworkListener { |
||||||
|
var ufw: CocosFramework! |
||||||
|
private var _isCocosPaused = false |
||||||
|
private var _isCocosReady = false |
||||||
|
private var _isCocosLoaded = false |
||||||
|
|
||||||
|
func initCocos() { |
||||||
|
if (self.cocosIsInitiallized()) { |
||||||
|
self.ufw?.showCocosWindow() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
self.ufw = CocosFrameworkLoad() |
||||||
|
|
||||||
|
self.ufw?.setDataBundleId("com.cocos3d.framework") |
||||||
|
|
||||||
|
registerCocosListener() |
||||||
|
self.ufw?.runEmbedded(withArgc: gArgc, argv: gArgv, appLaunchOpts: appLaunchOpts) |
||||||
|
|
||||||
|
if self.ufw?.appController() != nil { |
||||||
|
controller = self.ufw?.appController() |
||||||
|
controller?.cocosMessageHandler = self.cocosMessageHandlers |
||||||
|
controller?.cocosSceneLoadedHandler = self.cocosSceneLoadedHandlers |
||||||
|
self.ufw?.appController()?.window?.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue - 1) |
||||||
|
} |
||||||
|
_isCocosLoaded = true |
||||||
|
} |
||||||
|
|
||||||
|
// check if cocos is initiallized |
||||||
|
func cocosIsInitiallized() -> Bool { |
||||||
|
if self.ufw != nil { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// Create new cocos player |
||||||
|
func createPlayer(completed: @escaping (_ view: UIView?) -> Void) { |
||||||
|
if self.cocosIsInitiallized() && self._isCocosReady { |
||||||
|
completed(controller?.rootView) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(forName: NSNotification.Name("CocosReady"), object: nil, queue: OperationQueue.main, using: { note in |
||||||
|
self._isCocosReady = true |
||||||
|
completed(controller?.rootView) |
||||||
|
}) |
||||||
|
|
||||||
|
DispatchQueue.main.async { |
||||||
|
// if (sharedApplication == nil) { |
||||||
|
// sharedApplication = UIApplication.shared |
||||||
|
// } |
||||||
|
|
||||||
|
// Always keep Flutter window on top |
||||||
|
// let flutterUIWindow = sharedApplication?.keyWindow |
||||||
|
// flutterUIWindow?.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue + 1) // Always keep Flutter window in top |
||||||
|
// sharedApplication?.keyWindow?.windowLevel = UIWindow.Level(UIWindow.Level.normal.rawValue + 1) |
||||||
|
|
||||||
|
self.initCocos() |
||||||
|
|
||||||
|
cocos_warmed_up = true |
||||||
|
self._isCocosReady = true |
||||||
|
self._isCocosLoaded = true |
||||||
|
|
||||||
|
self.listenAppState() |
||||||
|
|
||||||
|
completed(controller?.rootView) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func registerCocosListener() { |
||||||
|
if self.cocosIsInitiallized() { |
||||||
|
self.ufw?.register(self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func unregisterCocosListener() { |
||||||
|
if self.cocosIsInitiallized() { |
||||||
|
self.ufw?.unregisterFrameworkListener(self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@objc |
||||||
|
public func cocosDidUnload(_ notification: Notification!) { |
||||||
|
unregisterCocosListener() |
||||||
|
self.ufw = nil |
||||||
|
self._isCocosReady = false |
||||||
|
self._isCocosLoaded = false |
||||||
|
} |
||||||
|
|
||||||
|
@objc func handleAppStateDidChange(notification: Notification?) { |
||||||
|
if !self._isCocosReady { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
let cocosAppController = self.ufw?.appController() as? CocosAppController |
||||||
|
let application = UIApplication.shared |
||||||
|
|
||||||
|
if notification?.name == UIApplication.willResignActiveNotification { |
||||||
|
cocosAppController?.applicationWillResignActive(application) |
||||||
|
} else if notification?.name == UIApplication.didEnterBackgroundNotification { |
||||||
|
cocosAppController?.applicationDidEnterBackground(application) |
||||||
|
} else if notification?.name == UIApplication.willEnterForegroundNotification { |
||||||
|
cocosAppController?.applicationWillEnterForeground(application) |
||||||
|
} else if notification?.name == UIApplication.didBecomeActiveNotification { |
||||||
|
cocosAppController?.applicationDidBecomeActive(application) |
||||||
|
} else if notification?.name == UIApplication.willTerminateNotification { |
||||||
|
cocosAppController?.applicationWillTerminate(application) |
||||||
|
} else if notification?.name == UIApplication.didReceiveMemoryWarningNotification { |
||||||
|
cocosAppController?.applicationDidReceiveMemoryWarning(application) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Listener for app lifecycle eventa |
||||||
|
func listenAppState() { |
||||||
|
for name in [ |
||||||
|
UIApplication.didBecomeActiveNotification, |
||||||
|
UIApplication.didEnterBackgroundNotification, |
||||||
|
UIApplication.willTerminateNotification, |
||||||
|
UIApplication.willResignActiveNotification, |
||||||
|
UIApplication.willEnterForegroundNotification, |
||||||
|
UIApplication.didReceiveMemoryWarningNotification |
||||||
|
] { |
||||||
|
NotificationCenter.default.addObserver( |
||||||
|
self, |
||||||
|
selector: #selector(self.handleAppStateDidChange), |
||||||
|
name: name, |
||||||
|
object: nil) |
||||||
|
} |
||||||
|
} |
||||||
|
// Pause cocos player |
||||||
|
func pause() { |
||||||
|
self.ufw?.pause(true) |
||||||
|
self._isCocosPaused = true |
||||||
|
} |
||||||
|
|
||||||
|
// Resume cocos player |
||||||
|
func resume() { |
||||||
|
self.ufw?.pause(false) |
||||||
|
self._isCocosPaused = false |
||||||
|
} |
||||||
|
|
||||||
|
// Unoad cocos player |
||||||
|
func unload() { |
||||||
|
self.ufw?.unloadApplication() |
||||||
|
} |
||||||
|
|
||||||
|
func isCocosLoaded() -> Bool { |
||||||
|
return _isCocosLoaded |
||||||
|
} |
||||||
|
|
||||||
|
func isCocosPaused() -> Bool { |
||||||
|
return _isCocosPaused |
||||||
|
} |
||||||
|
|
||||||
|
// Quit cocos player application |
||||||
|
func quit() { |
||||||
|
self.ufw?.quitApplication(0) |
||||||
|
self._isCocosLoaded = false |
||||||
|
} |
||||||
|
|
||||||
|
// Post message to cocos |
||||||
|
func postMessageToCocos(gameObject: String?, cocosMethodName: String?, cocosMessage: String?) { |
||||||
|
if self.cocosIsInitiallized() { |
||||||
|
self.ufw?.sendMessageToGO(withName: gameObject, functionName: cocosMethodName, message: cocosMessage) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Handle incoming cocos messages looping through all controllers and passing payload to |
||||||
|
/// the controller handler methods |
||||||
|
@objc |
||||||
|
func cocosMessageHandlers(_ message: UnsafePointer<Int8>?) { |
||||||
|
for c in globalControllers { |
||||||
|
if let strMsg = message { |
||||||
|
c.handleMessage(message: String(utf8String: strMsg) ?? "") |
||||||
|
} else { |
||||||
|
c.handleMessage(message: "") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func cocosSceneLoadedHandlers(name: UnsafePointer<Int8>?, buildIndex: UnsafePointer<Int32>?, isLoaded: UnsafePointer<Bool>?, isValid: UnsafePointer<Bool>?) { |
||||||
|
if let sceneName = name, |
||||||
|
let bIndex = buildIndex, |
||||||
|
let loaded = isLoaded, |
||||||
|
let valid = isValid { |
||||||
|
|
||||||
|
let loadedVal = Bool((Int(bitPattern: loaded) != 0)) |
||||||
|
let validVal = Bool((Int(bitPattern: valid) != 0)) |
||||||
|
|
||||||
|
let addObject: Dictionary<String, Any> = [ |
||||||
|
"name": String(utf8String: sceneName) ?? "", |
||||||
|
"buildIndex": Int(bitPattern: bIndex), |
||||||
|
"isLoaded": loadedVal, |
||||||
|
"isValid": validVal, |
||||||
|
] |
||||||
|
|
||||||
|
for c in globalControllers { |
||||||
|
c.handleSceneChangeEvent(info: addObject) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
// |
||||||
|
// FLTCocosOptionsSink.swift |
||||||
|
// flutter_unity_widget |
||||||
|
// |
||||||
|
// Created by Rex Raphael on 30/01/2021. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
|
||||||
|
// Defines map UI options writable from Flutter. |
||||||
|
protocol FLTCocosOptionsSink: AnyObject { |
||||||
|
func setDisabledUnload(enabled: Bool) |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
// |
||||||
|
// FLTCocosView.swift |
||||||
|
// flutter_unity_widget |
||||||
|
// |
||||||
|
// Created by Rex Raphael on 30/01/2021. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import UIKit |
||||||
|
|
||||||
|
|
||||||
|
class FLTCocosView: UIView { |
||||||
|
override func layoutSubviews() { |
||||||
|
super.layoutSubviews() |
||||||
|
if (!self.bounds.isEmpty) { |
||||||
|
GetCocosPlayerUtils().ufw?.appController()?.rootView.frame = self.bounds |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
// |
||||||
|
// FLTCocosViewFactory.swift |
||||||
|
// flutter_unity_widget |
||||||
|
// |
||||||
|
// Created by Rex Raphael on 30/01/2021. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
|
||||||
|
class FLTCocosWidgetFactory: NSObject, FlutterPlatformViewFactory { |
||||||
|
private weak var registrar: FlutterPluginRegistrar? |
||||||
|
|
||||||
|
init(registrar: NSObjectProtocol & FlutterPluginRegistrar) { |
||||||
|
super.init() |
||||||
|
self.registrar = registrar |
||||||
|
} |
||||||
|
|
||||||
|
func createArgsCodec() -> (NSObjectProtocol & FlutterMessageCodec) { |
||||||
|
return FlutterStandardMessageCodec.sharedInstance() |
||||||
|
} |
||||||
|
|
||||||
|
func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { |
||||||
|
let controller = FLTCocosWidgetController( |
||||||
|
frame: frame, |
||||||
|
viewIdentifier: viewId, |
||||||
|
arguments: args, |
||||||
|
registrar: registrar!) |
||||||
|
return controller |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,182 @@ |
|||||||
|
// |
||||||
|
// FLTCocosViewController.swift |
||||||
|
// flutter_cocos_widget |
||||||
|
// |
||||||
|
// Created by Rex Raphael on 30/01/2021. |
||||||
|
// |
||||||
|
|
||||||
|
import Foundation |
||||||
|
import CocosFramework |
||||||
|
|
||||||
|
// Defines cocos controllable from Flutter. |
||||||
|
public class FLTCocosWidgetController: NSObject, FLTCocosOptionsSink, FlutterPlatformView { |
||||||
|
private var _rootView: FLTCocosView |
||||||
|
private var viewId: Int64 = 0 |
||||||
|
private var channel: FlutterMethodChannel? |
||||||
|
private weak var registrar: (NSObjectProtocol & FlutterPluginRegistrar)? |
||||||
|
|
||||||
|
private var _disposed = false |
||||||
|
|
||||||
|
init( |
||||||
|
frame: CGRect, |
||||||
|
viewIdentifier viewId: Int64, |
||||||
|
arguments args: Any?, |
||||||
|
registrar: NSObjectProtocol & FlutterPluginRegistrar |
||||||
|
) { |
||||||
|
self._rootView = FLTCocosView(frame: frame) |
||||||
|
super.init() |
||||||
|
|
||||||
|
globalControllers.append(self) |
||||||
|
|
||||||
|
self.viewId = viewId |
||||||
|
|
||||||
|
let channelName = String(format: "plugin.xraph.com/cocos_view_%lld", viewId) |
||||||
|
self.channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger()) |
||||||
|
|
||||||
|
self.channel?.setMethodCallHandler(self.methodHandler) |
||||||
|
self.attachView() |
||||||
|
} |
||||||
|
|
||||||
|
func methodHandler(_ call: FlutterMethodCall, result: FlutterResult) { |
||||||
|
if call.method == "cocos#dispose" { |
||||||
|
self.dispose() |
||||||
|
result(nil) |
||||||
|
} else { |
||||||
|
self.reattachView() |
||||||
|
if call.method == "cocos#isReady" { |
||||||
|
result(GetCocosPlayerUtils().cocosIsInitiallized()) |
||||||
|
} else if call.method == "cocos#isLoaded" { |
||||||
|
let _isUnloaded = GetCocosPlayerUtils().isCocosLoaded() |
||||||
|
result(_isUnloaded) |
||||||
|
} else if call.method == "cocos#createCocosPlayer" { |
||||||
|
startCocosIfNeeded() |
||||||
|
result(nil) |
||||||
|
} else if call.method == "cocos#isPaused" { |
||||||
|
let _isPaused = GetCocosPlayerUtils().isCocosPaused() |
||||||
|
result(_isPaused) |
||||||
|
} else if call.method == "cocos#pausePlayer" { |
||||||
|
GetCocosPlayerUtils().pause() |
||||||
|
result(nil) |
||||||
|
} else if call.method == "cocos#postMessage" { |
||||||
|
self.postMessage(call: call, result: result) |
||||||
|
result(nil) |
||||||
|
} else if call.method == "cocos#resumePlayer" { |
||||||
|
GetCocosPlayerUtils().resume() |
||||||
|
result(nil) |
||||||
|
} else if call.method == "cocos#unloadPlayer" { |
||||||
|
GetCocosPlayerUtils().unload() |
||||||
|
result(nil) |
||||||
|
} else if call.method == "cocos#quitPlayer" { |
||||||
|
GetCocosPlayerUtils().quit() |
||||||
|
result(nil) |
||||||
|
} else if call.method == "cocos#waitForCocos" { |
||||||
|
result(nil) |
||||||
|
} else { |
||||||
|
result(FlutterMethodNotImplemented) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func setDisabledUnload(enabled: Bool) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public func view() -> UIView { |
||||||
|
return _rootView; |
||||||
|
} |
||||||
|
|
||||||
|
private func startCocosIfNeeded() { |
||||||
|
GetCocosPlayerUtils().createPlayer(completed: { [self] (view: UIView?) in |
||||||
|
|
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func attachView() { |
||||||
|
startCocosIfNeeded() |
||||||
|
|
||||||
|
let cocosView = GetCocosPlayerUtils().ufw?.appController()?.rootView |
||||||
|
if let superview = cocosView?.superview { |
||||||
|
cocosView?.removeFromSuperview() |
||||||
|
superview.layoutIfNeeded() |
||||||
|
} |
||||||
|
|
||||||
|
if let cocosView = cocosView { |
||||||
|
_rootView.addSubview(cocosView) |
||||||
|
_rootView.layoutIfNeeded() |
||||||
|
self.channel?.invokeMethod("events#onViewReattached", arguments: "") |
||||||
|
} |
||||||
|
GetCocosPlayerUtils().resume() |
||||||
|
} |
||||||
|
|
||||||
|
func reattachView() { |
||||||
|
let cocosView = GetCocosPlayerUtils().ufw?.appController()?.rootView |
||||||
|
let superview = cocosView?.superview |
||||||
|
if superview != _rootView { |
||||||
|
attachView() |
||||||
|
} |
||||||
|
|
||||||
|
GetCocosPlayerUtils().resume() |
||||||
|
} |
||||||
|
|
||||||
|
func removeViewIfNeeded() { |
||||||
|
if GetCocosPlayerUtils().ufw == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
let cocosView = GetCocosPlayerUtils().ufw?.appController()?.rootView |
||||||
|
if _rootView == cocosView?.superview { |
||||||
|
if globalControllers.isEmpty { |
||||||
|
cocosView?.removeFromSuperview() |
||||||
|
cocosView?.superview?.layoutIfNeeded() |
||||||
|
} else { |
||||||
|
globalControllers.last?.reattachView() |
||||||
|
} |
||||||
|
} |
||||||
|
GetCocosPlayerUtils().resume() |
||||||
|
} |
||||||
|
|
||||||
|
func dispose() { |
||||||
|
if _disposed { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
globalControllers.removeAll{ value in |
||||||
|
return value == self |
||||||
|
} |
||||||
|
|
||||||
|
channel?.setMethodCallHandler(nil) |
||||||
|
removeViewIfNeeded() |
||||||
|
|
||||||
|
_disposed = true |
||||||
|
} |
||||||
|
|
||||||
|
/// Handles messages from cocos in the current view |
||||||
|
func handleMessage(message: String) { |
||||||
|
self.channel?.invokeMethod("events#onCocosMessage", arguments: message) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/// Handles scene changed event from cocos in the current view |
||||||
|
func handleSceneChangeEvent(info: Dictionary<String, Any>) { |
||||||
|
self.channel?.invokeMethod("events#onCocosSceneLoaded", arguments: info) |
||||||
|
} |
||||||
|
|
||||||
|
/// Post messages to cocos from flutter |
||||||
|
func postMessage(call: FlutterMethodCall, result: FlutterResult) { |
||||||
|
guard let args = call.arguments else { |
||||||
|
result("iOS could not recognize flutter arguments in method: (postMessage)") |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if let myArgs = args as? [String: Any], |
||||||
|
let gObj = myArgs["gameObject"] as? String, |
||||||
|
let method = myArgs["methodName"] as? String, |
||||||
|
let message = myArgs["message"] as? String { |
||||||
|
GetCocosPlayerUtils().postMessageToCocos(gameObject: gObj, cocosMethodName: method, cocosMessage: message) |
||||||
|
result(nil) |
||||||
|
} else { |
||||||
|
result(FlutterError(code: "-1", message: "iOS could not extract " + |
||||||
|
"flutter arguments in method: (postMessage)", details: nil)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
//
|
||||||
|
// FlutterCocosWidgetPlugin.h
|
||||||
|
// FlutterCocosWidgetPlugin
|
||||||
|
//
|
||||||
|
// Created by Kris Pypen on 8/1/19.
|
||||||
|
// Updated by Rex Raphael on 8/27/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Flutter/Flutter.h> |
||||||
|
|
||||||
|
@interface FlutterCocosWidgetPlugin : NSObject<FlutterPlugin> |
||||||
|
@end |
||||||
|
|
@ -0,0 +1,22 @@ |
|||||||
|
#import FlutterCocosWidgetPlugin.h |
||||||
|
#import <Foundation/Foundation.h> |
||||||
|
#if __has_include(<flutter_unity_widget/flutter_unity_widget-Swift.h>) |
||||||
|
#import <flutter_unity_widget/flutter_unity_widget-Swift.h> |
||||||
|
#else |
||||||
|
// Support project import fallback if the generated compatibility header |
||||||
|
// is not copied when this plugin is created as a library. |
||||||
|
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 |
||||||
|
#import "flutter_unity_widget-Swift.h" |
||||||
|
#endif |
||||||
|
|
||||||
|
@implementation FlutterCocosWidgetPlugin { |
||||||
|
NSObject<FlutterPluginRegistrar>* _registrar; |
||||||
|
FlutterMethodChannel* _channel; |
||||||
|
NSMutableDictionary* _unityControllers; |
||||||
|
} |
||||||
|
|
||||||
|
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar { |
||||||
|
[SwiftFlutterCocosWidgetPlugin registerWithRegistrar:registrar]; |
||||||
|
} |
||||||
|
|
||||||
|
@end |
@ -1,17 +0,0 @@ |
|||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:flutter/services.dart'; |
|
||||||
|
|
||||||
import 'flutter_cocos_view_platform_interface.dart'; |
|
||||||
|
|
||||||
/// An implementation of [FlutterCocosViewPlatform] that uses method channels. |
|
||||||
class MethodChannelFlutterCocosView extends FlutterCocosViewPlatform { |
|
||||||
/// The method channel used to interact with the native platform. |
|
||||||
@visibleForTesting |
|
||||||
final methodChannel = const MethodChannel('flutter_cocos_view'); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<String?> getPlatformVersion() async { |
|
||||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion'); |
|
||||||
return version; |
|
||||||
} |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
|
||||||
|
|
||||||
import 'flutter_cocos_view_method_channel.dart'; |
|
||||||
|
|
||||||
abstract class FlutterCocosViewPlatform extends PlatformInterface { |
|
||||||
/// Constructs a FlutterCocosViewPlatform. |
|
||||||
FlutterCocosViewPlatform() : super(token: _token); |
|
||||||
|
|
||||||
static final Object _token = Object(); |
|
||||||
|
|
||||||
static FlutterCocosViewPlatform _instance = MethodChannelFlutterCocosView(); |
|
||||||
|
|
||||||
/// The default instance of [FlutterCocosViewPlatform] to use. |
|
||||||
/// |
|
||||||
/// Defaults to [MethodChannelFlutterCocosView]. |
|
||||||
static FlutterCocosViewPlatform get instance => _instance; |
|
||||||
|
|
||||||
/// Platform-specific implementations should set this with their own |
|
||||||
/// platform-specific class that extends [FlutterCocosViewPlatform] when |
|
||||||
/// they register themselves. |
|
||||||
static set instance(FlutterCocosViewPlatform instance) { |
|
||||||
PlatformInterface.verifyToken(instance, _token); |
|
||||||
_instance = instance; |
|
||||||
} |
|
||||||
|
|
||||||
Future<String?> getPlatformVersion() { |
|
||||||
throw UnimplementedError('platformVersion() has not been implemented.'); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,103 @@ |
|||||||
|
typedef void CocosCreatedCallback(CocosWidgetController controller); |
||||||
|
|
||||||
|
abstract class CocosWidgetController { |
||||||
|
static dynamic webRegistrar; |
||||||
|
|
||||||
|
/// Method required for web initialization |
||||||
|
static void registerWith(dynamic registrar) { |
||||||
|
webRegistrar = registrar; |
||||||
|
} |
||||||
|
|
||||||
|
/// Initialize [CocosWidgetController] with [id] |
||||||
|
/// Mainly for internal use when instantiating a [CocosWidgetController] passed |
||||||
|
/// in [CocosWidget.onCocosCreated] callback. |
||||||
|
static Future<CocosWidgetController> init( |
||||||
|
int id, dynamic CocosWidgetState) async { |
||||||
|
throw UnimplementedError('init() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Checks to see if Cocos player is ready to be used |
||||||
|
/// Returns `true` if Cocos player is ready. |
||||||
|
Future<bool?>? isReady() { |
||||||
|
throw UnimplementedError('isReady() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the current pause state of the Cocos player |
||||||
|
/// Returns `true` if Cocos player is paused. |
||||||
|
Future<bool?>? isPaused() { |
||||||
|
throw UnimplementedError('isPaused() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the current load state of the Cocos player |
||||||
|
/// Returns `true` if Cocos player is loaded. |
||||||
|
Future<bool?>? isLoaded() { |
||||||
|
throw UnimplementedError('isLoaded() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Helper method to know if Cocos has been put in background mode (WIP) unstable |
||||||
|
/// Returns `true` if Cocos player is in background. |
||||||
|
Future<bool?>? inBackground() { |
||||||
|
throw UnimplementedError('inBackground() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Creates a Cocos player if it's not already created. Please only call this if Cocos is not ready, |
||||||
|
/// or is in unloaded state. Use [isLoaded] to check. |
||||||
|
/// Returns `true` if Cocos player was created succesfully. |
||||||
|
Future<bool?>? create() { |
||||||
|
throw UnimplementedError('create() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Post message to Cocos from flutter. This method takes in a string [message]. |
||||||
|
/// The [gameObject] must match the name of an actual Cocos game object in a scene at runtime, and the [methodName], |
||||||
|
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method |
||||||
|
/// |
||||||
|
/// ```dart |
||||||
|
/// postMessage("GameManager", "openScene", "ThirdScene") |
||||||
|
/// ``` |
||||||
|
Future<void>? postMessage(String gameObject, methodName, message) { |
||||||
|
throw UnimplementedError('postMessage() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Post message to Cocos from flutter. This method takes in a Json or map structure as the [message]. |
||||||
|
/// The [gameObject] must match the name of an actual Cocos game object in a scene at runtime, and the [methodName], |
||||||
|
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method |
||||||
|
/// |
||||||
|
/// ```dart |
||||||
|
/// postJsonMessage("GameManager", "openScene", {"buildIndex": 3, "name": "ThirdScene"}) |
||||||
|
/// ``` |
||||||
|
Future<void>? postJsonMessage( |
||||||
|
String gameObject, String methodName, Map<String, dynamic> message) { |
||||||
|
throw UnimplementedError('postJsonMessage() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Pause the Cocos in-game player with this method |
||||||
|
Future<void>? pause() { |
||||||
|
throw UnimplementedError('pause() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Resume the Cocos in-game player with this method idf it is in a paused state |
||||||
|
Future<void>? resume() { |
||||||
|
throw UnimplementedError('resume() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Sometimes you want to open Cocos in it's own process and openInNativeProcess does just that. |
||||||
|
/// It works for Android and iOS is WIP |
||||||
|
Future<void>? openInNativeProcess() { |
||||||
|
throw UnimplementedError('openInNativeProcess() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Unloads Cocos player from th current process (Works on Android only for now) |
||||||
|
/// iOS is WIP. For more information please read [Cocos Docs](https://docs.Cocos3d.com/2020.2/Documentation/Manual/CocosasaLibrary.html) |
||||||
|
Future<void>? unload() { |
||||||
|
throw UnimplementedError('unload() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Quits Cocos player. Note that this kills the current flutter process, thus quiting the app |
||||||
|
Future<void>? quit() { |
||||||
|
throw UnimplementedError('quit() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
void dispose() { |
||||||
|
throw UnimplementedError('dispose() has not been implemented.'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:flutter/gestures.dart'; |
||||||
|
import 'package:flutter_cocos_view/src/facade_controller.dart'; |
||||||
|
|
||||||
|
import 'helpers/misc.dart'; |
||||||
|
|
||||||
|
class CocosWidget extends StatefulWidget { |
||||||
|
CocosWidget({ |
||||||
|
Key? key, |
||||||
|
required this.onCocosCreated, |
||||||
|
this.onCocosMessage, |
||||||
|
this.fullscreen = false, |
||||||
|
this.enablePlaceholder = false, |
||||||
|
this.runImmediately = false, |
||||||
|
this.unloadOnDispose = false, |
||||||
|
this.printSetupLog = true, |
||||||
|
this.onCocosUnloaded, |
||||||
|
this.gestureRecognizers, |
||||||
|
this.placeholder, |
||||||
|
this.useAndroidViewSurface = false, |
||||||
|
this.onCocosSceneLoaded, |
||||||
|
this.uiLevel = 1, |
||||||
|
this.borderRadius = BorderRadius.zero, |
||||||
|
this.layoutDirection, |
||||||
|
this.hideStatus = false, |
||||||
|
}); |
||||||
|
|
||||||
|
///Event fires when the Cocos player is created. |
||||||
|
final CocosCreatedCallback onCocosCreated; |
||||||
|
|
||||||
|
///Event fires when the [CocosWidget] gets a message from Cocos. |
||||||
|
final CocosMessageCallback? onCocosMessage; |
||||||
|
|
||||||
|
///Event fires when the [CocosWidget] gets a scene loaded from Cocos. |
||||||
|
final CocosSceneChangeCallback? onCocosSceneLoaded; |
||||||
|
|
||||||
|
///Event fires when the [CocosWidget] Cocos player gets unloaded. |
||||||
|
final CocosUnloadCallback? onCocosUnloaded; |
||||||
|
|
||||||
|
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; |
||||||
|
|
||||||
|
/// Set to true to force Cocos to fullscreen |
||||||
|
final bool fullscreen; |
||||||
|
|
||||||
|
/// Set to true to force Cocos to fullscreen |
||||||
|
final bool hideStatus; |
||||||
|
|
||||||
|
/// Controls the layer in which Cocos widget is rendered in flutter (defaults to 1) |
||||||
|
final int uiLevel; |
||||||
|
|
||||||
|
/// This flag tells android to load Cocos as the flutter app starts (Android only) |
||||||
|
final bool runImmediately; |
||||||
|
|
||||||
|
/// This flag tells android to unload Cocos whenever widget is disposed |
||||||
|
final bool unloadOnDispose; |
||||||
|
|
||||||
|
/// This flag enables placeholder widget |
||||||
|
final bool enablePlaceholder; |
||||||
|
|
||||||
|
/// This flag enables placeholder widget |
||||||
|
final bool printSetupLog; |
||||||
|
|
||||||
|
/// This flag allows you use AndroidView instead of PlatformViewLink for android |
||||||
|
final bool? useAndroidViewSurface; |
||||||
|
|
||||||
|
/// This is just a helper to render a placeholder widget |
||||||
|
final Widget? placeholder; |
||||||
|
|
||||||
|
/// Border radius |
||||||
|
final BorderRadius borderRadius; |
||||||
|
|
||||||
|
/// The layout direction to use for the embedded view. |
||||||
|
/// |
||||||
|
/// If this is null, the ambient [Directionality] is used instead. If there is |
||||||
|
/// no ambient [Directionality], [TextDirection.ltr] is used. |
||||||
|
final TextDirection? layoutDirection; |
||||||
|
|
||||||
|
@override |
||||||
|
_CocosWidgetState createState() => _CocosWidgetState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _CocosWidgetState extends State<CocosWidget> { |
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Text( |
||||||
|
'$defaultTargetPlatform is not yet supported by the Cocos player plugin'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
import 'package:flutter_cocos_view/src/helpers/types.dart'; |
||||||
|
|
||||||
|
class CocosEvent<T> { |
||||||
|
/// The ID of the Cocos this event is associated to. |
||||||
|
final int cocosId; |
||||||
|
|
||||||
|
/// The value wrapped by this event |
||||||
|
final T value; |
||||||
|
|
||||||
|
/// Build a Cocos Event, that relates a mapId with a given value. |
||||||
|
/// |
||||||
|
/// The `cocosId` is the id of the map that triggered the event. |
||||||
|
/// `value` may be `null` in events that don't transport any meaningful data. |
||||||
|
CocosEvent(this.cocosId, this.value); |
||||||
|
} |
||||||
|
|
||||||
|
class CocosSceneLoadedEvent extends CocosEvent<SceneLoaded?> { |
||||||
|
CocosSceneLoadedEvent(int cocosId, SceneLoaded? value) |
||||||
|
: super(cocosId, value); |
||||||
|
} |
||||||
|
|
||||||
|
class CocosLoadedEvent extends CocosEvent<void> { |
||||||
|
CocosLoadedEvent(int cocosId, void value) : super(cocosId, value); |
||||||
|
} |
||||||
|
|
||||||
|
class CocosUnLoadedEvent extends CocosEvent<void> { |
||||||
|
CocosUnLoadedEvent(int cocosId, void value) : super(cocosId, value); |
||||||
|
} |
||||||
|
|
||||||
|
class CocosCreatedEvent extends CocosEvent<void> { |
||||||
|
CocosCreatedEvent(int cocosId, void value) : super(cocosId, value); |
||||||
|
} |
||||||
|
|
||||||
|
class CocosMessageEvent extends CocosEvent<dynamic> { |
||||||
|
CocosMessageEvent(int cocosId, dynamic value) : super(cocosId, value); |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
import 'package:flutter_cocos_view/src/helpers/types.dart'; |
||||||
|
|
||||||
|
/// Error thrown when an unknown cocos ID is provided to a method channel API. |
||||||
|
class UnknownCocosIDError extends Error { |
||||||
|
/// Creates an assertion error with the provided [cocosId] and optional |
||||||
|
/// [message]. |
||||||
|
UnknownCocosIDError(this.cocosId, [this.message]); |
||||||
|
|
||||||
|
/// The unknown ID. |
||||||
|
final int cocosId; |
||||||
|
|
||||||
|
/// Message describing the assertion error. |
||||||
|
final Object? message; |
||||||
|
|
||||||
|
String toString() { |
||||||
|
if (message != null) { |
||||||
|
return "Unknown cocos ID $cocosId: ${Error.safeToString(message)}"; |
||||||
|
} |
||||||
|
return "Unknown cocos ID $cocosId"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
typedef void CocosMessageCallback(dynamic handler); |
||||||
|
|
||||||
|
typedef void CocosSceneChangeCallback(SceneLoaded? message); |
||||||
|
|
||||||
|
typedef void CocosUnloadCallback(); |
@ -0,0 +1,31 @@ |
|||||||
|
class SceneLoaded { |
||||||
|
final String? name; |
||||||
|
final int? buildIndex; |
||||||
|
final bool? isLoaded; |
||||||
|
final bool? isValid; |
||||||
|
|
||||||
|
SceneLoaded({this.name, this.buildIndex, this.isLoaded, this.isValid}); |
||||||
|
|
||||||
|
/// Mainly for internal use when calling [CameraUpdate.newCameraPosition]. |
||||||
|
dynamic toMap() => <String, dynamic>{ |
||||||
|
'name': name, |
||||||
|
'buildIndex': buildIndex, |
||||||
|
'isLoaded': isLoaded, |
||||||
|
'isValid': isValid, |
||||||
|
}; |
||||||
|
|
||||||
|
/// Deserializes [SceneLoaded] from a map. |
||||||
|
/// |
||||||
|
/// Mainly for internal use. |
||||||
|
static SceneLoaded? fromMap(dynamic json) { |
||||||
|
if (json == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return SceneLoaded( |
||||||
|
name: json['name'], |
||||||
|
buildIndex: json['buildIndex'], |
||||||
|
isLoaded: json['isLoaded'], |
||||||
|
isValid: json['isValid'], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,201 @@ |
|||||||
|
import 'dart:developer'; |
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:flutter/gestures.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
import '../facade_controller.dart'; |
||||||
|
import '../helpers/misc.dart'; |
||||||
|
import 'device_method.dart'; |
||||||
|
import 'mobile_cocos_widget_controller.dart'; |
||||||
|
import 'cocos_widget_platform.dart'; |
||||||
|
|
||||||
|
int _nextCocosCreationId = 0; |
||||||
|
|
||||||
|
/// Android specific settings for [CocosWidget]. |
||||||
|
class AndroidCocosWidgetFlutter { |
||||||
|
/// Whether to render [CocosWidget] with a [AndroidViewSurface] to build the Flutter Cocos widget. |
||||||
|
/// |
||||||
|
/// This implementation uses hybrid composition to render the Flutter Cocos |
||||||
|
/// Widget on Android. This comes at the cost of some performance on Android |
||||||
|
/// versions below 10. See |
||||||
|
/// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more |
||||||
|
/// information. |
||||||
|
/// |
||||||
|
/// Defaults to true. |
||||||
|
static bool get useAndroidViewSurface { |
||||||
|
final CocosWidgetPlatform platform = CocosWidgetPlatform.instance; |
||||||
|
if (platform is MethodChannelCocosWidget) { |
||||||
|
return platform.useAndroidViewSurface; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/// Set whether to render [CocosWidget] with a [AndroidViewSurface] to build the Flutter Cocos widget. |
||||||
|
/// |
||||||
|
/// This implementation uses hybrid composition to render the Cocos Widget |
||||||
|
/// Widget on Android. This comes at the cost of some performance on Android |
||||||
|
/// versions below 10. See |
||||||
|
/// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more |
||||||
|
/// information. |
||||||
|
/// |
||||||
|
/// Defaults to true. |
||||||
|
static set useAndroidViewSurface(bool useAndroidViewSurface) { |
||||||
|
final CocosWidgetPlatform platform = CocosWidgetPlatform.instance; |
||||||
|
if (platform is MethodChannelCocosWidget) { |
||||||
|
platform.useAndroidViewSurface = useAndroidViewSurface; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
typedef MobileCocosWidgetState = _CocosWidgetState; |
||||||
|
|
||||||
|
class CocosWidget extends StatefulWidget { |
||||||
|
CocosWidget({ |
||||||
|
Key? key, |
||||||
|
required this.onCocosCreated, |
||||||
|
this.onCocosMessage, |
||||||
|
this.fullscreen = false, |
||||||
|
this.enablePlaceholder = false, |
||||||
|
this.runImmediately = false, |
||||||
|
this.unloadOnDispose = false, |
||||||
|
this.printSetupLog = true, |
||||||
|
this.onCocosUnloaded, |
||||||
|
this.gestureRecognizers, |
||||||
|
this.placeholder, |
||||||
|
this.useAndroidViewSurface = false, |
||||||
|
this.onCocosSceneLoaded, |
||||||
|
this.uiLevel = 1, |
||||||
|
this.borderRadius = BorderRadius.zero, |
||||||
|
this.layoutDirection, |
||||||
|
this.hideStatus = false, |
||||||
|
this.webUrl, |
||||||
|
}); |
||||||
|
|
||||||
|
///Event fires when the Cocos player is created. |
||||||
|
final CocosCreatedCallback onCocosCreated; |
||||||
|
|
||||||
|
/// WebGL url source. |
||||||
|
final String? webUrl; |
||||||
|
|
||||||
|
///Event fires when the [CocosWidget] gets a message from Cocos. |
||||||
|
final CocosMessageCallback? onCocosMessage; |
||||||
|
|
||||||
|
///Event fires when the [CocosWidget] gets a scene loaded from Cocos. |
||||||
|
final CocosSceneChangeCallback? onCocosSceneLoaded; |
||||||
|
|
||||||
|
///Event fires when the [CocosWidget] Cocos player gets unloaded. |
||||||
|
final CocosUnloadCallback? onCocosUnloaded; |
||||||
|
|
||||||
|
final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; |
||||||
|
|
||||||
|
/// Set to true to force Cocos to fullscreen |
||||||
|
final bool fullscreen; |
||||||
|
|
||||||
|
/// Set to true to force Cocos to fullscreen |
||||||
|
final bool hideStatus; |
||||||
|
|
||||||
|
/// Controls the layer in which Cocos widget is rendered in flutter (defaults to 1) |
||||||
|
final int uiLevel; |
||||||
|
|
||||||
|
/// This flag tells android to load Cocos as the flutter app starts (Android only) |
||||||
|
final bool runImmediately; |
||||||
|
|
||||||
|
/// This flag tells android to unload Cocos whenever widget is disposed |
||||||
|
final bool unloadOnDispose; |
||||||
|
|
||||||
|
/// This flag enables placeholder widget |
||||||
|
final bool enablePlaceholder; |
||||||
|
|
||||||
|
/// This flag enables placeholder widget |
||||||
|
final bool printSetupLog; |
||||||
|
|
||||||
|
/// This flag allows you use AndroidView instead of PlatformViewLink for android |
||||||
|
final bool? useAndroidViewSurface; |
||||||
|
|
||||||
|
/// This is just a helper to render a placeholder widget |
||||||
|
final Widget? placeholder; |
||||||
|
|
||||||
|
/// Border radius |
||||||
|
final BorderRadius borderRadius; |
||||||
|
|
||||||
|
/// The layout direction to use for the embedded view. |
||||||
|
/// |
||||||
|
/// If this is null, the ambient [Directionality] is used instead. If there is |
||||||
|
/// no ambient [Directionality], [TextDirection.ltr] is used. |
||||||
|
final TextDirection? layoutDirection; |
||||||
|
|
||||||
|
@override |
||||||
|
_CocosWidgetState createState() => _CocosWidgetState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _CocosWidgetState extends State<CocosWidget> { |
||||||
|
late int _CocosId; |
||||||
|
|
||||||
|
CocosWidgetController? _controller; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
|
||||||
|
if (!kIsWeb) { |
||||||
|
_CocosId = _nextCocosCreationId++; |
||||||
|
} else { |
||||||
|
_CocosId = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> dispose() async { |
||||||
|
if (!kIsWeb) { |
||||||
|
if (_nextCocosCreationId > 0) --_nextCocosCreationId; |
||||||
|
} |
||||||
|
try { |
||||||
|
_controller?.dispose(); |
||||||
|
_controller = null; |
||||||
|
} catch (e) { |
||||||
|
// todo: implement |
||||||
|
} |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
final Map<String, dynamic> CocosOptions = <String, dynamic>{ |
||||||
|
'fullscreen': widget.fullscreen, |
||||||
|
'uiLevel': widget.uiLevel, |
||||||
|
'hideStatus': widget.hideStatus, |
||||||
|
'unloadOnDispose': widget.unloadOnDispose, |
||||||
|
'runImmediately': widget.runImmediately, |
||||||
|
}; |
||||||
|
|
||||||
|
if (widget.enablePlaceholder) { |
||||||
|
return widget.placeholder ?? |
||||||
|
Text('Placeholder mode enabled, no native code will be called'); |
||||||
|
} |
||||||
|
|
||||||
|
return CocosWidgetPlatform.instance.buildViewWithTextDirection( |
||||||
|
_CocosId, |
||||||
|
_onPlatformViewCreated, |
||||||
|
cocosOptions: CocosOptions, |
||||||
|
textDirection: widget.layoutDirection ?? |
||||||
|
Directionality.maybeOf(context) ?? |
||||||
|
TextDirection.ltr, |
||||||
|
gestureRecognizers: widget.gestureRecognizers, |
||||||
|
useAndroidViewSurf: widget.useAndroidViewSurface, |
||||||
|
cocosSrcUrl: widget.webUrl, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _onPlatformViewCreated(int id) async { |
||||||
|
final controller = await MobileCocosWidgetController.init(id, this); |
||||||
|
_controller = controller; |
||||||
|
widget.onCocosCreated(controller); |
||||||
|
|
||||||
|
if (widget.printSetupLog) { |
||||||
|
log('*********************************************'); |
||||||
|
log('** flutter Cocos controller setup complete **'); |
||||||
|
log('*********************************************'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,154 @@ |
|||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:flutter/gestures.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/services.dart'; |
||||||
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
||||||
|
|
||||||
|
import '../helpers/events.dart'; |
||||||
|
import 'device_method.dart'; |
||||||
|
|
||||||
|
abstract class CocosWidgetPlatform extends PlatformInterface { |
||||||
|
/// Constructs a CocosViewFlutterPlatform. |
||||||
|
CocosWidgetPlatform() : super(token: _token); |
||||||
|
|
||||||
|
static final Object _token = Object(); |
||||||
|
|
||||||
|
static CocosWidgetPlatform _instance = MethodChannelCocosWidget(); |
||||||
|
|
||||||
|
/// The default instance of [CocosWidgetPlatform] to use. |
||||||
|
/// |
||||||
|
/// Defaults to [MethodChannelCocosWidgetFlutter]. |
||||||
|
static CocosWidgetPlatform get instance => _instance; |
||||||
|
|
||||||
|
/// Platform-specific plugins should set this with their own platform-specific |
||||||
|
/// class that extends [CocosWidgetPlatform] when they register themselves. |
||||||
|
static set instance(CocosWidgetPlatform instance) { |
||||||
|
PlatformInterface.verifyToken(instance, _token); |
||||||
|
_instance = instance; |
||||||
|
} |
||||||
|
|
||||||
|
/// /// Initializes the platform interface with [id]. |
||||||
|
/// |
||||||
|
/// This method is called when the plugin is first initialized. |
||||||
|
Future<void> init(int cocosId) { |
||||||
|
throw UnimplementedError('init() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<bool?> isReady({required int cocosId}) async { |
||||||
|
throw UnimplementedError('init() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<bool?> isPaused({required int cocosId}) async { |
||||||
|
throw UnimplementedError('isPaused() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<bool?> isLoaded({required int cocosId}) async { |
||||||
|
throw UnimplementedError('isLoaded() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<bool?> inBackground({required int cocosId}) async { |
||||||
|
throw UnimplementedError('inBackground() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<bool?> createCocosPlayer({required int cocosId}) async { |
||||||
|
throw UnimplementedError('createCocosPlayer() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> postMessage( |
||||||
|
{required int cocosId, |
||||||
|
required String gameObject, |
||||||
|
required String methodName, |
||||||
|
required String message}) { |
||||||
|
throw UnimplementedError('postMessage() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> postJsonMessage( |
||||||
|
{required int cocosId, |
||||||
|
required String gameObject, |
||||||
|
required String methodName, |
||||||
|
required Map message}) { |
||||||
|
throw UnimplementedError('postJsonMessage() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> pausePlayer({required int cocosId}) async { |
||||||
|
throw UnimplementedError('pausePlayer() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> resumePlayer({required int cocosId}) async { |
||||||
|
throw UnimplementedError('resumePlayer() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Opens cocos in it's own activity. Android only. |
||||||
|
Future<void> openInNativeProcess({required int cocosId}) async { |
||||||
|
throw UnimplementedError('openInNativeProcess() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> unloadPlayer({required int cocosId}) async { |
||||||
|
throw UnimplementedError('unloadPlayer() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> quitPlayer({required int cocosId}) async { |
||||||
|
throw UnimplementedError('quitPlayer() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Stream<CocosMessageEvent> onCocosMessage({required int cocosId}) { |
||||||
|
throw UnimplementedError('onCocosMessage() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Stream<CocosLoadedEvent> onCocosUnloaded({required int cocosId}) { |
||||||
|
throw UnimplementedError('onCocosUnloaded() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Stream<CocosCreatedEvent> onCocosCreated({required int cocosId}) { |
||||||
|
throw UnimplementedError('onCocosUnloaded() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
Stream<CocosSceneLoadedEvent> onCocosSceneLoaded({required int cocosId}) { |
||||||
|
throw UnimplementedError('onCocosSceneLoaded() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Dispose of whatever resources the `cocosId` is holding on to. |
||||||
|
void dispose({required int cocosId}) { |
||||||
|
throw UnimplementedError('dispose() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns a widget displaying the cocos view |
||||||
|
Widget buildView( |
||||||
|
int creationId, |
||||||
|
PlatformViewCreatedCallback onPlatformViewCreated, { |
||||||
|
Map<String, dynamic> cocosOptions = const {}, |
||||||
|
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
||||||
|
bool? useAndroidViewSurf, |
||||||
|
String? cocosSrcUrl, |
||||||
|
}) { |
||||||
|
throw UnimplementedError('buildView() has not been implemented.'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns a widget displaying the cocos view. |
||||||
|
/// |
||||||
|
/// This method is similar to [buildView], but contains a parameter for |
||||||
|
/// platforms that require a text direction. |
||||||
|
/// |
||||||
|
/// Default behavior passes all parameters except `textDirection` to |
||||||
|
/// [buildView]. This is for backward compatibility with existing |
||||||
|
/// implementations. Platforms that use the text direction should override |
||||||
|
/// this as the primary implementation, and delegate to it from buildView. |
||||||
|
Widget buildViewWithTextDirection( |
||||||
|
int creationId, |
||||||
|
PlatformViewCreatedCallback onPlatformViewCreated, { |
||||||
|
required TextDirection textDirection, |
||||||
|
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
||||||
|
Map<String, dynamic> cocosOptions = const <String, dynamic>{}, |
||||||
|
bool? useAndroidViewSurf, |
||||||
|
String? cocosSrcUrl, |
||||||
|
}) { |
||||||
|
return buildView( |
||||||
|
creationId, |
||||||
|
onPlatformViewCreated, |
||||||
|
gestureRecognizers: gestureRecognizers, |
||||||
|
cocosOptions: cocosOptions, |
||||||
|
useAndroidViewSurf: useAndroidViewSurf, |
||||||
|
cocosSrcUrl: cocosSrcUrl, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,299 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
import 'dart:convert'; |
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:flutter/gestures.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/rendering.dart'; |
||||||
|
import 'package:flutter/services.dart'; |
||||||
|
import 'package:stream_transform/stream_transform.dart'; |
||||||
|
|
||||||
|
import '../helpers/events.dart'; |
||||||
|
import '../helpers/misc.dart'; |
||||||
|
import '../helpers/types.dart'; |
||||||
|
import 'cocos_widget_platform.dart'; |
||||||
|
import 'windows_cocos_widget_view.dart'; |
||||||
|
|
||||||
|
class MethodChannelCocosWidget extends CocosWidgetPlatform { |
||||||
|
// Every method call passes the int cocosId |
||||||
|
late final Map<int, MethodChannel> _channels = {}; |
||||||
|
|
||||||
|
/// Set [CocosWidgetFlutterPlatform] to use [AndroidViewSurface] to build the Google Maps widget. |
||||||
|
/// |
||||||
|
/// This implementation uses hybrid composition to render the Cocos Widget |
||||||
|
/// Widget on Android. This comes at the cost of some performance on Android |
||||||
|
/// versions below 10. See |
||||||
|
/// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more |
||||||
|
/// information. |
||||||
|
/// Defaults to false. |
||||||
|
bool useAndroidViewSurface = true; |
||||||
|
|
||||||
|
/// Accesses the MethodChannel associated to the passed cocosId. |
||||||
|
MethodChannel channel(int cocosId) { |
||||||
|
MethodChannel? channel = _channels[cocosId]; |
||||||
|
if (channel == null) { |
||||||
|
throw UnknownCocosIDError(cocosId); |
||||||
|
} |
||||||
|
return channel; |
||||||
|
} |
||||||
|
|
||||||
|
MethodChannel ensureChannelInitialized(int cocosId) { |
||||||
|
MethodChannel? channel = _channels[cocosId]; |
||||||
|
if (channel == null) { |
||||||
|
channel = MethodChannel('plugin.xraph.com/cocos_view_$cocosId'); |
||||||
|
|
||||||
|
channel.setMethodCallHandler( |
||||||
|
(MethodCall call) => _handleMethodCall(call, cocosId)); |
||||||
|
_channels[cocosId] = channel; |
||||||
|
} |
||||||
|
return channel; |
||||||
|
} |
||||||
|
|
||||||
|
/// Initializes the platform interface with [id]. |
||||||
|
/// |
||||||
|
/// This method is called when the plugin is first initialized. |
||||||
|
@override |
||||||
|
Future<void> init(int cocosId) { |
||||||
|
MethodChannel channel = ensureChannelInitialized(cocosId); |
||||||
|
return channel.invokeMethod<void>('cocos#waitForCocos'); |
||||||
|
} |
||||||
|
|
||||||
|
/// Dispose of the native resources. |
||||||
|
@override |
||||||
|
Future<void> dispose({int? cocosId}) async { |
||||||
|
try { |
||||||
|
if (cocosId != null) await channel(cocosId).invokeMethod('cocos#dispose'); |
||||||
|
} catch (e) { |
||||||
|
// ignore |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// The controller we need to broadcast the different events coming |
||||||
|
// from handleMethodCall. |
||||||
|
// |
||||||
|
// It is a `broadcast` because multiple controllers will connect to |
||||||
|
// different stream views of this Controller. |
||||||
|
final StreamController<CocosEvent> _cocosStreamController = |
||||||
|
StreamController<CocosEvent>.broadcast(); |
||||||
|
|
||||||
|
// Returns a filtered view of the events in the _controller, by cocosId. |
||||||
|
Stream<CocosEvent> _events(int cocosId) => |
||||||
|
_cocosStreamController.stream.where((event) => event.cocosId == cocosId); |
||||||
|
|
||||||
|
Future<dynamic> _handleMethodCall(MethodCall call, int cocosId) async { |
||||||
|
switch (call.method) { |
||||||
|
case "events#onCocosMessage": |
||||||
|
_cocosStreamController.add(CocosMessageEvent(cocosId, call.arguments)); |
||||||
|
break; |
||||||
|
case "events#onCocosUnloaded": |
||||||
|
_cocosStreamController.add(CocosLoadedEvent(cocosId, call.arguments)); |
||||||
|
break; |
||||||
|
case "events#onCocosSceneLoaded": |
||||||
|
_cocosStreamController.add(CocosSceneLoadedEvent( |
||||||
|
cocosId, SceneLoaded.fromMap(call.arguments))); |
||||||
|
break; |
||||||
|
case "events#onCocosCreated": |
||||||
|
_cocosStreamController.add(CocosCreatedEvent(cocosId, call.arguments)); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw UnimplementedError("Unimplemented ${call.method} method"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<bool?> isPaused({required int cocosId}) async { |
||||||
|
return await channel(cocosId).invokeMethod('cocos#isPaused'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<bool?> isReady({required int cocosId}) async { |
||||||
|
return await channel(cocosId).invokeMethod('cocos#isReady'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<bool?> isLoaded({required int cocosId}) async { |
||||||
|
return await channel(cocosId).invokeMethod('cocos#isLoaded'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<bool?> inBackground({required int cocosId}) async { |
||||||
|
return await channel(cocosId).invokeMethod('cocos#inBackground'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<bool?> createCocosPlayer({required int cocosId}) async { |
||||||
|
return await channel(cocosId).invokeMethod('cocos#createPlayer'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Stream<CocosMessageEvent> onCocosMessage({required int cocosId}) { |
||||||
|
return _events(cocosId).whereType<CocosMessageEvent>(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Stream<CocosLoadedEvent> onCocosUnloaded({required int cocosId}) { |
||||||
|
return _events(cocosId).whereType<CocosLoadedEvent>(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Stream<CocosCreatedEvent> onCocosCreated({required int cocosId}) { |
||||||
|
return _events(cocosId).whereType<CocosCreatedEvent>(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Stream<CocosSceneLoadedEvent> onCocosSceneLoaded({required int cocosId}) { |
||||||
|
return _events(cocosId).whereType<CocosSceneLoadedEvent>(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget buildViewWithTextDirection( |
||||||
|
int creationId, |
||||||
|
PlatformViewCreatedCallback onPlatformViewCreated, { |
||||||
|
required TextDirection textDirection, |
||||||
|
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
||||||
|
Map<String, dynamic> cocosOptions = const <String, dynamic>{}, |
||||||
|
bool? useAndroidViewSurf, |
||||||
|
bool? height, |
||||||
|
bool? width, |
||||||
|
bool? cocosWebSource, |
||||||
|
String? cocosSrcUrl, |
||||||
|
}) { |
||||||
|
final String _viewType = 'plugin.xraph.com/cocos_view'; |
||||||
|
|
||||||
|
if (useAndroidViewSurf != null) useAndroidViewSurface = useAndroidViewSurf; |
||||||
|
|
||||||
|
final Map<String, dynamic> creationParams = cocosOptions; |
||||||
|
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.windows) { |
||||||
|
return WindowsCocosWidgetView(); |
||||||
|
} |
||||||
|
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.android) { |
||||||
|
if (!useAndroidViewSurface) { |
||||||
|
return AndroidView( |
||||||
|
viewType: _viewType, |
||||||
|
onPlatformViewCreated: onPlatformViewCreated, |
||||||
|
gestureRecognizers: gestureRecognizers, |
||||||
|
creationParams: creationParams, |
||||||
|
creationParamsCodec: const StandardMessageCodec(), |
||||||
|
hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
||||||
|
layoutDirection: TextDirection.ltr, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return PlatformViewLink( |
||||||
|
viewType: _viewType, |
||||||
|
surfaceFactory: ( |
||||||
|
BuildContext context, |
||||||
|
PlatformViewController controller, |
||||||
|
) { |
||||||
|
return AndroidViewSurface( |
||||||
|
controller: controller as AndroidViewController, |
||||||
|
gestureRecognizers: gestureRecognizers ?? |
||||||
|
const <Factory<OneSequenceGestureRecognizer>>{}, |
||||||
|
hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
||||||
|
); |
||||||
|
}, |
||||||
|
onCreatePlatformView: (PlatformViewCreationParams params) { |
||||||
|
final controller = PlatformViewsService.initExpensiveAndroidView( |
||||||
|
id: params.id, |
||||||
|
viewType: _viewType, |
||||||
|
layoutDirection: TextDirection.ltr, |
||||||
|
creationParams: creationParams, |
||||||
|
creationParamsCodec: const StandardMessageCodec(), |
||||||
|
onFocus: () => params.onFocusChanged(true), |
||||||
|
); |
||||||
|
|
||||||
|
controller |
||||||
|
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) |
||||||
|
..addOnPlatformViewCreatedListener(onPlatformViewCreated) |
||||||
|
..create(); |
||||||
|
return controller; |
||||||
|
}, |
||||||
|
); |
||||||
|
} else if (defaultTargetPlatform == TargetPlatform.iOS) { |
||||||
|
return UiKitView( |
||||||
|
viewType: _viewType, |
||||||
|
onPlatformViewCreated: onPlatformViewCreated, |
||||||
|
gestureRecognizers: gestureRecognizers, |
||||||
|
creationParams: creationParams, |
||||||
|
creationParamsCodec: const StandardMessageCodec(), |
||||||
|
); |
||||||
|
} |
||||||
|
return Text( |
||||||
|
'$defaultTargetPlatform is not yet supported by the cocos player plugin'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget buildView( |
||||||
|
int creationId, |
||||||
|
PlatformViewCreatedCallback onPlatformViewCreated, { |
||||||
|
Map<String, dynamic> cocosOptions = const {}, |
||||||
|
Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers, |
||||||
|
bool? useAndroidViewSurf, |
||||||
|
String? cocosSrcUrl, |
||||||
|
}) { |
||||||
|
return buildViewWithTextDirection( |
||||||
|
creationId, |
||||||
|
onPlatformViewCreated, |
||||||
|
textDirection: TextDirection.ltr, |
||||||
|
gestureRecognizers: gestureRecognizers, |
||||||
|
cocosOptions: cocosOptions, |
||||||
|
useAndroidViewSurf: useAndroidViewSurf, |
||||||
|
cocosSrcUrl: cocosSrcUrl, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> postMessage({ |
||||||
|
required int cocosId, |
||||||
|
required String gameObject, |
||||||
|
required String methodName, |
||||||
|
required String message, |
||||||
|
}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#postMessage', <String, dynamic>{ |
||||||
|
'gameObject': gameObject, |
||||||
|
'methodName': methodName, |
||||||
|
'message': message, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> postJsonMessage({ |
||||||
|
required int cocosId, |
||||||
|
required String gameObject, |
||||||
|
required String methodName, |
||||||
|
required Map message, |
||||||
|
}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#postMessage', <String, dynamic>{ |
||||||
|
'gameObject': gameObject, |
||||||
|
'methodName': methodName, |
||||||
|
'message': json.encode(message), |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> pausePlayer({required int cocosId}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#pausePlayer'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> resumePlayer({required int cocosId}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#resumePlayer'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> openInNativeProcess({required int cocosId}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#openInNativeProcess'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> unloadPlayer({required int cocosId}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#unloadPlayer'); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> quitPlayer({required int cocosId}) async { |
||||||
|
await channel(cocosId).invokeMethod('cocos#quitPlayer'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
export '../facade_controller.dart'; |
||||||
|
export '../helpers/events.dart'; |
||||||
|
export '../helpers/misc.dart'; |
||||||
|
export '../helpers/types.dart'; |
@ -0,0 +1,213 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter/services.dart'; |
||||||
|
|
||||||
|
import '../facade_controller.dart'; |
||||||
|
import '../helpers/events.dart'; |
||||||
|
import 'device_method.dart'; |
||||||
|
import 'cocos_widget.dart'; |
||||||
|
import 'cocos_widget_platform.dart'; |
||||||
|
|
||||||
|
class MobileCocosWidgetController extends CocosWidgetController { |
||||||
|
final MobileCocosWidgetState _cocosWidgetState; |
||||||
|
|
||||||
|
/// The cocosId for this controller |
||||||
|
final int cocosId; |
||||||
|
|
||||||
|
/// used for cancel the subscription |
||||||
|
StreamSubscription? _onCocosMessageSub, |
||||||
|
_onCocosSceneLoadedSub, |
||||||
|
_onCocosUnloadedSub; |
||||||
|
|
||||||
|
MobileCocosWidgetController._(this._cocosWidgetState, |
||||||
|
{required this.cocosId}) { |
||||||
|
_connectStreams(cocosId); |
||||||
|
} |
||||||
|
|
||||||
|
/// Initialize [CocosWidgetController] with [id] |
||||||
|
/// Mainly for internal use when instantiating a [CocosWidgetController] passed |
||||||
|
/// in [CocosWidget.onCocosCreated] callback. |
||||||
|
static Future<MobileCocosWidgetController> init( |
||||||
|
int id, MobileCocosWidgetState cocosWidgetState) async { |
||||||
|
await CocosWidgetPlatform.instance.init(id); |
||||||
|
return MobileCocosWidgetController._( |
||||||
|
cocosWidgetState, |
||||||
|
cocosId: id, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@visibleForTesting |
||||||
|
MethodChannel? get channel { |
||||||
|
if (CocosWidgetPlatform.instance is MethodChannelCocosWidget) { |
||||||
|
return (CocosWidgetPlatform.instance as MethodChannelCocosWidget) |
||||||
|
.channel(cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
void _connectStreams(int cocosId) { |
||||||
|
if (_cocosWidgetState.widget.onCocosMessage != null) { |
||||||
|
_onCocosMessageSub = CocosWidgetPlatform.instance |
||||||
|
.onCocosMessage(cocosId: cocosId) |
||||||
|
.listen((CocosMessageEvent e) => |
||||||
|
_cocosWidgetState.widget.onCocosMessage!(e.value)); |
||||||
|
} |
||||||
|
|
||||||
|
if (_cocosWidgetState.widget.onCocosSceneLoaded != null) { |
||||||
|
_onCocosSceneLoadedSub = CocosWidgetPlatform.instance |
||||||
|
.onCocosSceneLoaded(cocosId: cocosId) |
||||||
|
.listen((CocosSceneLoadedEvent e) => |
||||||
|
_cocosWidgetState.widget.onCocosSceneLoaded!(e.value)); |
||||||
|
} |
||||||
|
|
||||||
|
if (_cocosWidgetState.widget.onCocosUnloaded != null) { |
||||||
|
_onCocosUnloadedSub = CocosWidgetPlatform.instance |
||||||
|
.onCocosUnloaded(cocosId: cocosId) |
||||||
|
.listen((_) => _cocosWidgetState.widget.onCocosUnloaded!()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Checks to see if cocos player is ready to be used |
||||||
|
/// Returns `true` if cocos player is ready. |
||||||
|
Future<bool?>? isReady() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.isReady(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the current pause state of the cocos player |
||||||
|
/// Returns `true` if cocos player is paused. |
||||||
|
Future<bool?>? isPaused() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.isPaused(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the current load state of the cocos player |
||||||
|
/// Returns `true` if cocos player is loaded. |
||||||
|
Future<bool?>? isLoaded() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.isLoaded(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Helper method to know if Cocos has been put in background mode (WIP) unstable |
||||||
|
/// Returns `true` if cocos player is in background. |
||||||
|
Future<bool?>? inBackground() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.inBackground(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Creates a cocos player if it's not already created. Please only call this if cocos is not ready, |
||||||
|
/// or is in unloaded state. Use [isLoaded] to check. |
||||||
|
/// Returns `true` if cocos player was created succesfully. |
||||||
|
Future<bool?>? create() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.createCocosPlayer(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Post message to cocos from flutter. This method takes in a string [message]. |
||||||
|
/// The [gameObject] must match the name of an actual cocos game object in a scene at runtime, and the [methodName], |
||||||
|
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method |
||||||
|
/// |
||||||
|
/// ```dart |
||||||
|
/// postMessage("GameManager", "openScene", "ThirdScene") |
||||||
|
/// ``` |
||||||
|
Future<void>? postMessage(String gameObject, methodName, message) { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.postMessage( |
||||||
|
cocosId: cocosId, |
||||||
|
gameObject: gameObject, |
||||||
|
methodName: methodName, |
||||||
|
message: message, |
||||||
|
); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Post message to cocos from flutter. This method takes in a Json or map structure as the [message]. |
||||||
|
/// The [gameObject] must match the name of an actual cocos game object in a scene at runtime, and the [methodName], |
||||||
|
/// must exist in a `MonoDevelop` `class` and also exposed as a method. [message] is an parameter taken by the method |
||||||
|
/// |
||||||
|
/// ```dart |
||||||
|
/// postJsonMessage("GameManager", "openScene", {"buildIndex": 3, "name": "ThirdScene"}) |
||||||
|
/// ``` |
||||||
|
Future<void>? postJsonMessage( |
||||||
|
String gameObject, String methodName, Map<String, dynamic> message) { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.postJsonMessage( |
||||||
|
cocosId: cocosId, |
||||||
|
gameObject: gameObject, |
||||||
|
methodName: methodName, |
||||||
|
message: message, |
||||||
|
); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Pause the cocos in-game player with this method |
||||||
|
Future<void>? pause() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.pausePlayer(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Resume the cocos in-game player with this method idf it is in a paused state |
||||||
|
Future<void>? resume() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.resumePlayer(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Sometimes you want to open cocos in it's own process and openInNativeProcess does just that. |
||||||
|
/// It works for Android and iOS is WIP |
||||||
|
Future<void>? openInNativeProcess() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.openInNativeProcess(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Unloads cocos player from th current process (Works on Android only for now) |
||||||
|
/// iOS is WIP. For more information please read [Cocos Docs](https://docs.cocos3d.com/2020.2/Documentation/Manual/CocosasaLibrary.html) |
||||||
|
Future<void>? unload() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.unloadPlayer(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Quits cocos player. Note that this kills the current flutter process, thus quiting the app |
||||||
|
Future<void>? quit() { |
||||||
|
if (!_cocosWidgetState.widget.enablePlaceholder) { |
||||||
|
return CocosWidgetPlatform.instance.quitPlayer(cocosId: cocosId); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/// cancel the subscriptions when dispose called |
||||||
|
void _cancelSubscriptions() { |
||||||
|
_onCocosMessageSub?.cancel(); |
||||||
|
_onCocosSceneLoadedSub?.cancel(); |
||||||
|
_onCocosUnloadedSub?.cancel(); |
||||||
|
|
||||||
|
_onCocosMessageSub = null; |
||||||
|
_onCocosSceneLoadedSub = null; |
||||||
|
_onCocosUnloadedSub = null; |
||||||
|
} |
||||||
|
|
||||||
|
void dispose() { |
||||||
|
_cancelSubscriptions(); |
||||||
|
CocosWidgetPlatform.instance.dispose(cocosId: cocosId); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
class WindowsCocosWidgetView extends StatefulWidget { |
||||||
|
const WindowsCocosWidgetView({super.key}); |
||||||
|
|
||||||
|
@override |
||||||
|
State<WindowsCocosWidgetView> createState() => _WindowsCocosWidgetViewState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _WindowsCocosWidgetViewState extends State<WindowsCocosWidgetView> { |
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
// TODO: Rex Update windows view |
||||||
|
return const MouseRegion( |
||||||
|
child: Texture( |
||||||
|
textureId: 0, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,27 +0,0 @@ |
|||||||
import 'package:flutter/services.dart'; |
|
||||||
import 'package:flutter_test/flutter_test.dart'; |
|
||||||
import 'package:flutter_cocos_view/flutter_cocos_view_method_channel.dart'; |
|
||||||
|
|
||||||
void main() { |
|
||||||
TestWidgetsFlutterBinding.ensureInitialized(); |
|
||||||
|
|
||||||
MethodChannelFlutterCocosView platform = MethodChannelFlutterCocosView(); |
|
||||||
const MethodChannel channel = MethodChannel('flutter_cocos_view'); |
|
||||||
|
|
||||||
setUp(() { |
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( |
|
||||||
channel, |
|
||||||
(MethodCall methodCall) async { |
|
||||||
return '42'; |
|
||||||
}, |
|
||||||
); |
|
||||||
}); |
|
||||||
|
|
||||||
tearDown(() { |
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); |
|
||||||
}); |
|
||||||
|
|
||||||
test('getPlatformVersion', () async { |
|
||||||
expect(await platform.getPlatformVersion(), '42'); |
|
||||||
}); |
|
||||||
} |
|
@ -1,29 +0,0 @@ |
|||||||
import 'package:flutter_test/flutter_test.dart'; |
|
||||||
import 'package:flutter_cocos_view/flutter_cocos_view.dart'; |
|
||||||
import 'package:flutter_cocos_view/flutter_cocos_view_platform_interface.dart'; |
|
||||||
import 'package:flutter_cocos_view/flutter_cocos_view_method_channel.dart'; |
|
||||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
|
||||||
|
|
||||||
class MockFlutterCocosViewPlatform |
|
||||||
with MockPlatformInterfaceMixin |
|
||||||
implements FlutterCocosViewPlatform { |
|
||||||
|
|
||||||
@override |
|
||||||
Future<String?> getPlatformVersion() => Future.value('42'); |
|
||||||
} |
|
||||||
|
|
||||||
void main() { |
|
||||||
final FlutterCocosViewPlatform initialPlatform = FlutterCocosViewPlatform.instance; |
|
||||||
|
|
||||||
test('$MethodChannelFlutterCocosView is the default instance', () { |
|
||||||
expect(initialPlatform, isInstanceOf<MethodChannelFlutterCocosView>()); |
|
||||||
}); |
|
||||||
|
|
||||||
test('getPlatformVersion', () async { |
|
||||||
FlutterCocosView flutterCocosViewPlugin = FlutterCocosView(); |
|
||||||
MockFlutterCocosViewPlatform fakePlatform = MockFlutterCocosViewPlatform(); |
|
||||||
FlutterCocosViewPlatform.instance = fakePlatform; |
|
||||||
|
|
||||||
expect(await flutterCocosViewPlugin.getPlatformVersion(), '42'); |
|
||||||
}); |
|
||||||
} |
|
Loading…
Reference in new issue