Skip to content

Commit

Permalink
Merge pull request #905 from toaster/feature/submenus
Browse files Browse the repository at this point in the history
Feature: submenus
  • Loading branch information
andydotxyz authored Apr 29, 2020
2 parents 0de6218 + 2535851 commit 8b361ec
Show file tree
Hide file tree
Showing 28 changed files with 835 additions and 130 deletions.
12 changes: 11 additions & 1 deletion cmd/fyne_demo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,17 @@ func main() {

w := a.NewWindow("Fyne Demo")

newItem := fyne.NewMenuItem("New", func() { fmt.Println("Menu New") })
newItem := fyne.NewMenuItem("New", nil)
otherItem := fyne.NewMenuItem("Other", nil)
otherItem.ChildMenu = fyne.NewMenu("",
fyne.NewMenuItem("Project", func() { fmt.Println("Menu New->Other->Project") }),
fyne.NewMenuItem("Mail", func() { fmt.Println("Menu New->Other->Mail") }),
)
newItem.ChildMenu = fyne.NewMenu("",
fyne.NewMenuItem("File", func() { fmt.Println("Menu New->File") }),
fyne.NewMenuItem("Directory", func() { fmt.Println("Menu New->Directory") }),
otherItem,
)
settingsItem := fyne.NewMenuItem("Settings", func() { fmt.Println("Menu Settings") })

cutItem := fyne.NewMenuItem("Cut", func() { fmt.Println("Menu Cut") })
Expand Down
21 changes: 11 additions & 10 deletions cmd/fyne_demo/screens/advanced.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,20 @@ func AdvancedScreen(win fyne.Window) fyne.CanvasObject {
})
}

return widget.NewHBox(widget.NewVBox(screen,
widget.NewButton("Custom Theme", func() {
fyne.CurrentApp().Settings().SetTheme(newCustomTheme())
}),
widget.NewButton("Fullscreen", func() {
win.SetFullScreen(!win.FullScreen())
}),
),

return widget.NewHBox(
widget.NewVBox(screen,
widget.NewButton("Custom Theme", func() {
fyne.CurrentApp().Settings().SetTheme(newCustomTheme())
}),
widget.NewButton("Fullscreen", func() {
win.SetFullScreen(!win.FullScreen())
}),
),
fyne.NewContainerWithLayout(layout.NewBorderLayout(label, nil, nil, nil),
label,
fyne.NewContainerWithLayout(layout.NewGridLayout(2),
generic, desk,
),
))
),
)
}
27 changes: 27 additions & 0 deletions cmd/fyne_demo/screens/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,28 @@ func makeButtonTab() fyne.Widget {
grid.ShowLineNumbers = true
grid.ShowWhitespace = true

shareItem := fyne.NewMenuItem("Share via", nil)
shareItem.ChildMenu = fyne.NewMenu("",
fyne.NewMenuItem("Twitter", func() { fmt.Println("context menu Share->Twitter") }),
fyne.NewMenuItem("Reddit", func() { fmt.Println("context menu Share->Reddit") }),
)
menuLabel := &contextMenuButton{
widget.NewButton("tap me for pop-up menu with submenus", nil),
fyne.NewMenu("",
fyne.NewMenuItem("Copy", func() { fmt.Println("context menu copy") }),
shareItem,
),
}

return widget.NewVBox(
widget.NewButton("Button (text only)", func() { fmt.Println("tapped text button") }),
widget.NewButtonWithIcon("Button (text & icon)", theme.ConfirmIcon(), func() { fmt.Println("tapped text & icon button") }),
disabled,
grid,
layout.NewSpacer(),
layout.NewSpacer(),
menuLabel,
layout.NewSpacer(),
)
}

Expand Down Expand Up @@ -273,3 +290,13 @@ func WidgetScreen() fyne.CanvasObject {
),
)
}

type contextMenuButton struct {
*widget.Button
menu *fyne.Menu
}

// Tapped satisfies the fyne.Tappable interface.
func (b *contextMenuButton) Tapped(e *fyne.PointEvent) {
widget.ShowPopUpMenuAtPosition(b.menu, fyne.CurrentApp().Driver().CanvasForObject(b), e.AbsolutePosition)
}
202 changes: 149 additions & 53 deletions internal/driver/glfw/menu_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,135 @@ import (
#include <AppKit/AppKit.h>
// Using void* as type for pointers is a workaround. See https://github.com/golang/go/issues/12065.
const void* darwinAppMenu();
const void* createDarwinMenu(const char* label);
void insertDarwinMenuItem(const void* menu, const char* label, int id, int index, bool isSeparator);
void assignDarwinSubmenu(const void*, const void*);
void completeDarwinMenu(void* menu, bool prepend);
const void* createDarwinMenu(const char* label);
const void* darwinAppMenu();
const void* insertDarwinMenuItem(const void* menu, const char* label, int id, int index, bool isSeparator);
// Used for tests.
const void* test_darwinMainMenu();
const void* test_NSMenu_itemAtIndex(const void*, NSInteger);
NSInteger test_NSMenu_numberOfItems(const void*);
void test_NSMenu_performActionForItemAtIndex(const void*, NSInteger);
const char* test_NSMenu_title(const void*);
bool test_NSMenu_isSeparatorItem(const void*);
const void* test_NSMenuItem_submenu(const void*);
const char* test_NSMenuItem_title(const void*);
*/
import "C"

var callbacks []func()
var ecb func(string)

//export menuCallback
func menuCallback(id int) {
callbacks[id]()
func addNativeMenu(w *window, menu *fyne.Menu, nextItemID int, prepend bool) int {
menu, nextItemID = handleSpecialItems(w, menu, nextItemID, true)

containsItems := false
for _, item := range menu.Items {
if !item.IsSeparator {
containsItems = true
break
}
}
if !containsItems {
return nextItemID
}

nsMenu, nextItemID := createNativeMenu(w, menu, nextItemID)
C.completeDarwinMenu(nsMenu, C.bool(prepend))
return nextItemID
}

func addNativeSubMenu(w *window, nsParentMenuItem unsafe.Pointer, menu *fyne.Menu, nextItemID int) int {
nsMenu, nextItemID := createNativeMenu(w, menu, nextItemID)
C.assignDarwinSubmenu(nsParentMenuItem, nsMenu)
return nextItemID
}

func createNativeMenu(w *window, menu *fyne.Menu, nextItemID int) (unsafe.Pointer, int) {
nsMenu := C.createDarwinMenu(C.CString(menu.Label))
for _, item := range menu.Items {
nsMenuItem := C.insertDarwinMenuItem(
nsMenu,
C.CString(item.Label),
C.int(nextItemID),
C.int(-1),
C.bool(item.IsSeparator),
)
nextItemID = registerCallback(w, item, nextItemID)
if item.ChildMenu != nil {
nextItemID = addNativeSubMenu(w, nsMenuItem, item.ChildMenu, nextItemID)
}
}
return nsMenu, nextItemID
}

//export exceptionCallback
func exceptionCallback(e *C.char) {
msg := C.GoString(e)
if ecb == nil {
panic("unhandled Obj-C exception: " + msg)
}
ecb(msg)
}

func handleSpecialItems(w *window, menu *fyne.Menu, nextItemID int, addSeparator bool) (*fyne.Menu, int) {
for i, item := range menu.Items {
if item.Label == "Settings" || item.Label == "Settings…" || item.Label == "Preferences" || item.Label == "Preferences…" {
items := make([]*fyne.MenuItem, 0, len(menu.Items)-1)
items = append(items, menu.Items[:i]...)
items = append(items, menu.Items[i+1:]...)
menu, nextItemID = handleSpecialItems(w, fyne.NewMenu(menu.Label, items...), nextItemID, false)

C.insertDarwinMenuItem(
C.darwinAppMenu(),
C.CString(item.Label),
C.int(nextItemID),
C.int(1),
C.bool(false),
)
if addSeparator {
C.insertDarwinMenuItem(
C.darwinAppMenu(),
C.CString(""),
C.int(nextItemID),
C.int(1),
C.bool(true),
)
}
nextItemID = registerCallback(w, item, nextItemID)
break
}
}
return menu, nextItemID
}

func registerCallback(w *window, item *fyne.MenuItem, nextItemID int) int {
if !item.IsSeparator {
callbacks = append(callbacks, func() {
if item.Action != nil {
w.queueEvent(item.Action)
}
})
nextItemID++
}
return nextItemID
}

func setExceptionCallback(cb func(string)) {
ecb = cb
}

func hasNativeMenu() bool {
return true
}

//export menuCallback
func menuCallback(id int) {
callbacks[id]()
}

func setupNativeMenu(w *window, main *fyne.MainMenu) {
nextItemID := 0
var helpMenu *fyne.Menu
Expand All @@ -49,54 +160,39 @@ func setupNativeMenu(w *window, main *fyne.MainMenu) {
}
}

func addNativeMenu(w *window, menu *fyne.Menu, nextItemID int, prepend bool) int {
createMenu := false
for _, item := range menu.Items {
if item.Label != "Settings" {
createMenu = true
break
}
}
//
// Test support methods
// These are needed because CGo is not supported inside test files.
//

var nsMenu unsafe.Pointer
if createMenu {
nsMenu = C.createDarwinMenu(C.CString(menu.Label))
}
func testDarwinMainMenu() unsafe.Pointer {
return C.test_darwinMainMenu()
}

for _, item := range menu.Items {
if item.Label == "Settings" {
C.insertDarwinMenuItem(
C.darwinAppMenu(),
C.CString(""),
C.int(nextItemID),
C.int(1),
C.bool(true),
)
C.insertDarwinMenuItem(
C.darwinAppMenu(),
C.CString(item.Label),
C.int(nextItemID),
C.int(2),
C.bool(false),
)
} else {
C.insertDarwinMenuItem(
nsMenu,
C.CString(item.Label),
C.int(nextItemID),
C.int(-1),
C.bool(item.IsSeparator),
)
}
if !item.IsSeparator {
action := item.Action // catch
callbacks = append(callbacks, func() { w.queueEvent(action) })
nextItemID++
}
}
func testNSMenuItemAtIndex(m unsafe.Pointer, i int) unsafe.Pointer {
return C.test_NSMenu_itemAtIndex(m, C.long(i))
}

if nsMenu != nil {
C.completeDarwinMenu(nsMenu, C.bool(prepend))
}
return nextItemID
func testNSMenuNumberOfItems(m unsafe.Pointer) int {
return int(C.test_NSMenu_numberOfItems(m))
}

func testNSMenuPerformActionForItemAtIndex(m unsafe.Pointer, i int) {
C.test_NSMenu_performActionForItemAtIndex(m, C.long(i))
}

func testNSMenuTitle(m unsafe.Pointer) string {
return C.GoString(C.test_NSMenu_title(m))
}

func testNSMenuIsSeparatorItem(i unsafe.Pointer) bool {
return bool(C.test_NSMenu_isSeparatorItem(i))
}

func testNSMenuItemSubmenu(i unsafe.Pointer) unsafe.Pointer {
return C.test_NSMenuItem_submenu(i)
}

func testNSMenuItemTitle(i unsafe.Pointer) string {
return C.GoString(C.test_NSMenuItem_title(i))
}
Loading

0 comments on commit 8b361ec

Please sign in to comment.