package objects import "image" import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/tomo/input" // import "git.tebibyte.media/tomo/tomo/event" import "git.tebibyte.media/tomo/objects/layouts" // Menu is a menu window. // // Sub-components: // - TearLine is a horizontal line at the top of the menu that, when clicked, // causes the menu to be "torn off" into a movable window. type Menu struct { tomo.Window parent tomo.Window bounds image.Rectangle rootContainer tomo.ContainerBox tearLine tomo.Object torn bool } // NewMenu creates a new menu with the specified items. The menu will appear // directly under the mouse pointer. func NewMenu (parent tomo.Window, items ...tomo.Object) (*Menu, error) { bounds := (image.Rectangle { }).Add(parent.MousePosition()) return newMenu(parent, bounds, items...) } // NewAnchoredMenu creates a new menu with the specified items. The menu will // appear directly under the anchor. func NewAnchoredMenu (anchor tomo.Object, items ...tomo.Object) (*Menu, error) { parent := anchor.GetBox().Window() bounds := menuBoundsFromAnchor(anchor) return newMenu(parent, bounds, items...) } func newMenu (parent tomo.Window, bounds image.Rectangle, items ...tomo.Object) (*Menu, error) { menu := &Menu { } menu.bounds = bounds menu.parent = parent window, err := menu.parent.NewMenu(menu.bounds) if err != nil { return nil, err } menu.Window = window menu.rootContainer = tomo.NewContainerBox() menu.rootContainer.SetAttr(tomo.ALayout(layouts.ContractVertical)) if !menu.torn { menu.tearLine = menu.newTearLine() menu.rootContainer.Add(menu.tearLine) } for _, item := range items { menu.rootContainer.Add(item) if item, ok := item.(*MenuItem); ok { item.OnClick(func () { if !menu.torn { menu.Close() } }) } } menu.rootContainer.SetRole(tomo.R("objects", "Container")) menu.rootContainer.SetTag("menu", true) menu.Window.SetRoot(menu.rootContainer) return menu, nil } // TearOff converts this menu into a tear-off menu. func (this *Menu) TearOff () { if this.torn { return } if this.parent == nil { return } this.torn = true window, err := this.parent.NewChild(this.bounds) window.SetIcon(tomo.IconListChoose) if err != nil { return } visible := this.Window.Visible() this.Window.SetRoot(nil) this.Window.Close() this.rootContainer.Remove(this.tearLine) this.rootContainer.SetTag("torn", true) this.Window = window this.Window.SetRoot(this.rootContainer) this.Window.SetVisible(visible) } func (this *Menu) newTearLine () tomo.Object { tearLine := tomo.NewBox() tearLine.SetRole(tomo.R("objects", "TearLine")) tearLine.SetFocusable(true) tearLine.OnMouseEnter(func () { tearLine.SetFocused(true) }) tearLine.OnMouseLeave(func () { tearLine.SetFocused(false) }) tearLine.OnKeyDown(func (key input.Key, numberPad bool) bool { if !isClickingKey(key) { return false } return true }) tearLine.OnKeyUp(func (key input.Key, numberPad bool) bool { if !isClickingKey(key) { return false } this.TearOff() return true }) tearLine.OnButtonDown(func (button input.Button) bool { if !isClickingButton(button) { return false } return true }) tearLine.OnButtonUp(func (button input.Button) bool { if !isClickingButton(button) { return false } if tearLine.Window().MousePosition().In(tearLine.Bounds()) { this.TearOff() } return true }) return tearLine } func menuBoundsFromAnchor (anchor tomo.Object) image.Rectangle { bounds := anchor.GetBox().Bounds() return image.Rect ( bounds.Min.X, bounds.Max.Y, bounds.Max.X, bounds.Max.Y)//.Add(windowBounds.Min) }