Skip to content

Commit

Permalink
display context menu on windows
Browse files Browse the repository at this point in the history
  • Loading branch information
maxence-charriere committed Dec 29, 2018
1 parent 392483e commit b63f65a
Show file tree
Hide file tree
Showing 11 changed files with 568 additions and 1 deletion.
Binary file modified cmd/goapp/uwp/x64/App.xbf
Binary file not shown.
Binary file modified cmd/goapp/uwp/x64/WindowPage.xbf
Binary file not shown.
Binary file modified cmd/goapp/uwp/x64/uwp.dll
Binary file not shown.
20 changes: 20 additions & 0 deletions drivers/win/driver_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ func (d *Driver) Run(c app.DriverConfig) error {
d.goRPC.Handle("windows.OnCallback", handleWindow(onWindowCallback))
d.goRPC.Handle("windows.OnNavigate", handleWindow(onWindowNavigate))

d.goRPC.Handle("menus.OnClose", handleMenu(onMenuClose))
d.goRPC.Handle("menus.OnCallback", handleMenu(onMenuCallback))

ctx, cancel := context.WithCancel(context.Background())
d.stop = cancel
defer cancel()
Expand Down Expand Up @@ -194,6 +197,23 @@ func (d *Driver) NewWindow(c app.WindowConfig) app.Window {
return newWindow(c)
}

// NewContextMenu satisfies the app.Driver interface.
func (d *Driver) NewContextMenu(c app.MenuConfig) app.Menu {
m := newMenu(c, "context menu")
if m.Err() != nil {
return m
}

err := d.winRPC.Call("driver.SetContextMenu", nil, struct {
ID string
}{
ID: m.ID(),
})

m.SetErr(err)
return m
}

// UI satisfies the app.Driver interface.
func (d *Driver) UI(f func()) {
d.ui <- f
Expand Down
189 changes: 189 additions & 0 deletions drivers/win/menu.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// +build windows

package win

import (
"encoding/json"
"fmt"
"net/url"

"github.com/google/uuid"
"github.com/murlokswarm/app"
"github.com/murlokswarm/app/internal/bridge"
"github.com/murlokswarm/app/internal/core"
"github.com/murlokswarm/app/internal/dom"
"github.com/pkg/errors"
)

// Menu implements the app.Menu interface.
type Menu struct {
core.Menu

id string
dom dom.Engine
typ string
compo app.Compo
keepWhenClosed bool
}

func newMenu(c app.MenuConfig, typ string) *Menu {
m := &Menu{
id: uuid.New().String(),
dom: dom.Engine{
Factory: driver.factory,
Resources: driver.Resources,
AllowedNodes: []string{
"menu",
"menuitem",
},
UI: driver.UI,
},
typ: typ,
}

m.dom.Sync = m.render

if err := driver.winRPC.Call("menus.New", nil, struct {
ID string
}{
ID: m.id,
}); err != nil {
m.SetErr(err)
return m
}

driver.elems.Put(m)

if len(c.URL) != 0 {
m.Load(c.URL)
}

return m
}

// ID satisfies the app.Menu interface.
func (m *Menu) ID() string {
return m.id
}

// Load satisfies the app.Menu interface.
func (m *Menu) Load(urlFmt string, v ...interface{}) {
var err error
defer func() {
m.SetErr(err)
}()

u := fmt.Sprintf(urlFmt, v...)
n := core.CompoNameFromURLString(u)

var c app.Compo
if c, err = driver.factory.NewCompo(n); err != nil {
return
}

m.compo = c

if err = driver.winRPC.Call("menus.Load", nil, struct {
ID string
}{
ID: m.id,
}); err != nil {
return
}

err = m.dom.New(c)
if err != nil {
return
}

if nav, ok := c.(app.Navigable); ok {
navURL, _ := url.Parse(u)
nav.OnNavigate(navURL)
}
}

// Compo satisfies the app.Menu interface.
func (m *Menu) Compo() app.Compo {
return m.compo
}

// Contains satisfies the app.Menu interface.
func (m *Menu) Contains(c app.Compo) bool {
return m.dom.Contains(c)
}

// Render satisfies the app.Menu interface.
func (m *Menu) Render(c app.Compo) {
m.SetErr(m.dom.Render(c))
}

func (m *Menu) render(changes interface{}) error {
b, err := json.Marshal(changes)
if err != nil {
return errors.Wrap(err, "encode changes failed")
}

return driver.winRPC.Call("menus.Render", nil, struct {
ID string
Changes string
}{
ID: m.id,
Changes: string(b),
})
}

// Type satisfies the app.Menu interface.
func (m *Menu) Type() string {
return m.typ
}

func onMenuCallback(m *Menu, in map[string]interface{}) interface{} {
mappingStr := in["Mapping"].(string)

var mapping dom.Mapping
if err := json.Unmarshal([]byte(mappingStr), &mapping); err != nil {
app.Logf("menu callback failed: %s", err)
return nil
}

c, err := m.dom.CompoByID(mapping.CompoID)
if err != nil {
app.Logf("menu callback failed: %s", err)
return nil
}

var f func()
if f, err = mapping.Map(c); err != nil {
app.Logf("menu callback failed: %s", err)
return nil
}

if f != nil {
f()
return nil
}

app.Render(c)
return nil
}

func onMenuClose(m *Menu, in map[string]interface{}) interface{} {
driver.elems.Delete(m)
return nil
}

func handleMenu(h func(m *Menu, in map[string]interface{}) interface{}) bridge.GoRPCHandler {
return func(in map[string]interface{}) interface{} {
id, _ := in["ID"].(string)
e := driver.elems.GetByID(id)

switch m := e.(type) {
case *Menu:
return h(m, in)

default:
app.Panic("menu not supported")
return nil
}
}
}
26 changes: 26 additions & 0 deletions drivers/win/uwp/uwp/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public App()
this.InitializeComponent();
this.Suspending += OnSuspending;

Bridge.Handle("driver.SetContextMenu", this.setContextMenu);

Bridge.Handle("windows.New", WindowPage.NewWindow);
Bridge.Handle("windows.Load", WindowPage.Load);
Bridge.Handle("windows.Render", WindowPage.Render);
Expand All @@ -48,6 +50,30 @@ public App()
Bridge.Handle("windows.Focus", WindowPage.Focus);
Bridge.Handle("windows.FullScreen", WindowPage.FullScreen);
Bridge.Handle("windows.ExitFullScreen", WindowPage.ExitFullScreen);

Bridge.Handle("menus.New", Menu.New);
Bridge.Handle("menus.Load", Menu.Load);
Bridge.Handle("menus.Render", Menu.Render);
}

private async void setContextMenu(JsonObject input, string returnID)
{
await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
try
{
var frame = Window.Current.Content as Frame;
var win = frame.Content as WindowPage;
var menu = Bridge.GetElem<Menu>(input.GetNamedString("ID"));
win.setContextMenu(menu);
Bridge.Return(returnID, null, null);
}
catch (Exception e)
{
Bridge.Return(returnID, null, e.Message);
}
});
}

protected override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
Expand Down
6 changes: 5 additions & 1 deletion drivers/win/uwp/uwp/WindowPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid x:Name="Root">
<WebView x:Name="Webview" DefaultBackgroundColor="Transparent" />
<WebView x:Name="Webview" DefaultBackgroundColor="Transparent">
<FlyoutBase.AttachedFlyout>
<MenuFlyout></MenuFlyout>
</FlyoutBase.AttachedFlyout>
</WebView>
</Grid>
</Page>
28 changes: 28 additions & 0 deletions drivers/win/uwp/uwp/WindowPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,5 +427,33 @@ internal static async void ExitFullScreen(JsonObject input, string returnID)
}
});
}

public void setContextMenu(Menu menu)
{
var pos = Window.Current.CoreWindow.PointerPosition;
pos.X -= Window.Current.Bounds.X;
pos.Y -= Window.Current.Bounds.Y;

var root = menu.CompoRoot(menu.Root) as MenuContainer;
var flyout = new MenuFlyout();

flyout.Closed += async (s, e) =>
{
Bridge.DeleteElem(menu.ID);
var input = new JsonObject();
input["ID"] = JsonValue.CreateStringValue(menu.ID);
await Bridge.GoCall("menus.OnClose", input, true);
};

foreach (var item in root.item.Items)
{
flyout.Items.Add(item);
}

FlyoutBase.SetAttachedFlyout(this.Webview, flyout);
flyout.ShowAt(this.Webview, pos);
}
}
}

0 comments on commit b63f65a

Please sign in to comment.