diff --git a/application.go b/application.go index 3d2b488f..cf55e1d6 100644 --- a/application.go +++ b/application.go @@ -36,13 +36,15 @@ func NewApplication(opt ...Option) *Application { config: defaultApplicationConfig, } - // The platformPlugin and textinputPlugin are currently hardcoded as we have - // a hard link with GLFW. The plugins must be singleton and are accessed - // directly from the flutter package to wire up with glfw. If there's going - // to be a renderer interface, it's init would replace this configuration. + // The platformPlugin, textinputPlugin, etc. are currently hardcoded as we + // have a hard link with GLFW. The plugins must be singleton and are + // accessed directly from the flutter package to wire up with glfw. If + // there's going to be a renderer interface, it's init would replace this + // configuration. opt = append(opt, AddPlugin(defaultNavigationPlugin)) opt = append(opt, AddPlugin(defaultPlatformPlugin)) opt = append(opt, AddPlugin(defaultTextinputPlugin)) + opt = append(opt, AddPlugin(defaultLifecyclePlugin)) // apply all configs for _, o := range opt { @@ -180,6 +182,8 @@ func (a *Application) Run() error { a.window.SetKeyCallback(defaultTextinputPlugin.glfwKeyCallback) a.window.SetCharCallback(defaultTextinputPlugin.glfwCharCallback) + a.window.SetIconifyCallback(defaultLifecyclePlugin.glfwIconifyCallback) + a.window.SetCursorEnterCallback(m.glfwCursorEnterCallback) a.window.SetCursorPosCallback(m.glfwCursorPosCallback) a.window.SetMouseButtonCallback(m.glfwMouseButtonCallback) diff --git a/lifecycle.go b/lifecycle.go new file mode 100644 index 00000000..e7aae4fe --- /dev/null +++ b/lifecycle.go @@ -0,0 +1,40 @@ +package flutter + +import ( + "fmt" + + "github.com/go-flutter-desktop/go-flutter/plugin" + "github.com/go-gl/glfw/v3.2/glfw" +) + +const lifecycleChannelName = "flutter/lifecycle" + +// lifecyclePlugin implements flutter.Plugin and handles method calls to the +// flutter/lifecycle channel. +type lifecyclePlugin struct { + channel *plugin.BasicMessageChannel +} + +// all hardcoded because theres not pluggable renderer system. +var defaultLifecyclePlugin = &lifecyclePlugin{} + +var _ Plugin = &lifecyclePlugin{} // compile-time type check + +func (p *lifecyclePlugin) InitPlugin(messenger plugin.BinaryMessenger) error { + p.channel = plugin.NewBasicMessageChannel(messenger, lifecycleChannelName, plugin.StringCodec{}) + return nil +} + +func (p *lifecyclePlugin) glfwIconifyCallback(w *glfw.Window, iconified bool) { + var state string + switch iconified { + case true: + state = "AppLifecycleState.paused" + case false: + state = "AppLifecycleState.resumed" + } + _, err := p.channel.Send(state) + if err != nil { + fmt.Printf("go-flutter: Failed to send lifecycle event %s: %v\n", state, err) + } +} diff --git a/navigation.go b/navigation.go index f172d859..53dfcd6b 100644 --- a/navigation.go +++ b/navigation.go @@ -7,7 +7,7 @@ import ( const navigationChannelName = "flutter/navigation" // navigationPlugin implements flutter.Plugin and handles method calls to the -// flutter/platform channel. +// flutter/navigation channel. type navigationPlugin struct { channel *plugin.MethodChannel } diff --git a/platform.go b/platform.go index 191c509c..3f7167db 100644 --- a/platform.go +++ b/platform.go @@ -13,6 +13,8 @@ import ( // platformPlugin implements flutter.Plugin and handles method calls to the // flutter/platform channel. type platformPlugin struct { + popBehavior PopBehaviorKind + messenger plugin.BinaryMessenger glfwTasker *tasker.Tasker window *glfw.Window @@ -20,7 +22,9 @@ type platformPlugin struct { } // hardcoded because there is no swappable renderer interface. -var defaultPlatformPlugin = &platformPlugin{} +var defaultPlatformPlugin = &platformPlugin{ + popBehavior: PopBehaviorNone, +} var _ Plugin = &platformPlugin{} // compile-time type check var _ PluginGLFW = &platformPlugin{} // compile-time type check @@ -37,6 +41,7 @@ func (p *platformPlugin) InitPluginGLFW(window *glfw.Window) (err error) { p.channel.HandleFunc("Clipboard.setData", p.handleClipboardSetData) p.channel.HandleFunc("Clipboard.getData", p.handleClipboardGetData) p.channel.HandleFunc("SystemChrome.setApplicationSwitcherDescription", p.handleWindowSetTitle) + p.channel.HandleFunc("SystemNavigator.pop", p.handleSystemNavigatorPop) return nil } diff --git a/plugin/method-channel.go b/plugin/method-channel.go index fac13fb7..99b9a51f 100644 --- a/plugin/method-channel.go +++ b/plugin/method-channel.go @@ -55,10 +55,13 @@ func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) (result if err != nil { return nil, errors.Wrap(err, "failed to send methodcall") } + // TODO(GeertJohan): InvokeMethod may not return any JSON. In Java this is + // handled by not having a callback handler, which means no reponse is + // expected and reponse is never unmarshalled. We should perhaps define + // InvokeMethod(..) and InovkeMethodNoResponse(..) to avoid errors when no + // response is given. + // https://github.com/go-flutter-desktop/go-flutter/issues/141 result, err = m.methodCodec.DecodeEnvelope(encodedReply) - if flutterError, ok := err.(*FlutterError); ok { - return nil, flutterError - } if err != nil { return nil, err } diff --git a/pop.go b/pop.go new file mode 100644 index 00000000..06cf3294 --- /dev/null +++ b/pop.go @@ -0,0 +1,67 @@ +package flutter + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// PopBehaviorKind defines how an application should handle the navigation pop +// event from the flutter side. +type PopBehaviorKind int + +const ( + // PopBehaviorNone means the system navigation pop event is ignored. + PopBehaviorNone PopBehaviorKind = iota + // PopBehaviorHide hides the application window on a system navigation pop + // event. + PopBehaviorHide + // PopBehaviorIconify minimizes/iconifies the application window on a system + // navigation pop event. + PopBehaviorIconify + // PopBehaviorClose closes the application on a system navigation pop event. + PopBehaviorClose +) + +// PopBehavior sets the PopBehavior on the application +func PopBehavior(popBehavior PopBehaviorKind) Option { + return func(c *config) { + // TODO: this is a workarround because there is no renderer interface + // yet. We rely on a platform plugin singleton to handle events from the + // flutter side. Should go via Application and renderer abstraction + // layer. + // + // Downside of this workarround is that it will configure the pop + // behavior for all Application's within the same Go process. + defaultPlatformPlugin.popBehavior = popBehavior + } +} + +func (p *platformPlugin) handleSystemNavigatorPop(arguments interface{}) (reply interface{}, err error) { + switch p.popBehavior { + case PopBehaviorNone: + return nil, nil + case PopBehaviorHide: + p.glfwTasker.Do(func() { + p.window.Hide() + }) + return nil, nil + case PopBehaviorIconify: + var err error + p.glfwTasker.Do(func() { + err = p.window.Iconify() + }) + if err != nil { + fmt.Printf("go-flutter: error on iconifying window: %v\n", err) + return nil, errors.Wrap(err, "failed to iconify window") + } + return nil, nil + case PopBehaviorClose: + p.glfwTasker.Do(func() { + p.window.SetShouldClose(true) + }) + return nil, nil + default: + return nil, errors.Errorf("unknown pop behavior %T not implemented by platform handler", p.popBehavior) + } +}