diff --git a/supervisor.go b/supervisor.go index 46f0ee4..51cca1d 100644 --- a/supervisor.go +++ b/supervisor.go @@ -88,6 +88,9 @@ type Supervisor struct { // List of window focus history for Window Manager. winFocus *FocusedWindow winBottom *FocusedWindow // pointer to bottom-most window + + // Widgets that we should draw on top, such as Tooltips. + onTop []Widget } // WidgetSlot holds a widget with a unique ID number in a sorted list. @@ -103,6 +106,7 @@ func NewSupervisor() *Supervisor { hovering: map[int]interface{}{}, clicked: map[int]bool{}, modals: []Widget{}, + onTop: []Widget{}, dd: NewDragDrop(), } } @@ -493,6 +497,16 @@ func (s *Supervisor) Present(e render.Engine) { modal.Present(e, modal.Point()) } } + + // Render any "on top" widgets like Tooltips. + if len(s.onTop) > 0 { + for _, widget := range s.onTop { + if widget.Hidden() { + continue + } + widget.Present(e, widget.Point()) + } + } } // Add a widget to be supervised. Has no effect if the widget is already @@ -561,3 +575,24 @@ func (s *Supervisor) GetModal() Widget { } return s.modals[len(s.modals)-1] } + +/* +DrawOnTop gives the Supervisor a widget to manage the presentation of, for +example the Tooltip. + +If you call Supervisor.Present() in your program's main loop, it will draw the +widgets that it manages, such as Windows, Menus and Tooltips. Call that function +last in your main loop, and these things are drawn on top of the rest of your +UI which you had called Present() on prior. + +The current draw order of the Supervisor is as follows: + +1. Managed windows are drawn in the order of most recently focused on top. +2. Pop-up modals such as Menus are drawn. Modals have an "event grab" and all + mouse events go to them, or clicking outside of them dismisses the modals. +3. DrawOnTop widgets such as Tooltips that should always be drawn "last" so as + not to be overwritten by neighboring widgets. +*/ +func (s *Supervisor) DrawOnTop(w Widget) { + s.onTop = append(s.onTop, w) +} diff --git a/tooltip.go b/tooltip.go index 1c73d0e..272cd23 100644 --- a/tooltip.go +++ b/tooltip.go @@ -12,7 +12,11 @@ func init() { precomputeArrows() } -// Tooltip attaches a mouse-over popup to another widget. +/* +Tooltip attaches a mouse-over popup to another widget. + + +*/ type Tooltip struct { BaseWidget @@ -20,6 +24,7 @@ type Tooltip struct { Text string // Text to show in the tooltip. TextVariable *string // String pointer instead of text. Edge Edge // side to display tooltip on + supervisor *Supervisor style *style.Tooltip target Widget @@ -68,7 +73,9 @@ func NewTooltip(target Widget, tt Tooltip) *Tooltip { return nil }) target.Handle(Present, func(ed EventData) error { - w.Present(ed.Engine, w.Point()) + if w.supervisor == nil { + w.Present(ed.Engine, w.Point()) + } return nil }) @@ -81,6 +88,24 @@ func NewTooltip(target Widget, tt Tooltip) *Tooltip { return w } +/* +Supervise the tooltip widget. This will put the rendering of this widget under the +Supervisor's care to be drawn "on top" of all other widgets. Your main loop should +call the Supervisor.Present() function lastly so that things managed by it (such as +Windows, Menus and Tooltips) draw on top of everything else. + +If you don't call this, the Tooltip by default will present when its attached widget +presents (if moused over and tooltip is to be visible). This alone is fine in many +simple use cases, but in a densely packed UI layout and depending on the Edge the +tooltip draws at, it may get over-drawn by other widgets and not appear "on top." +*/ +func (w *Tooltip) Supervise(s *Supervisor) { + w.supervisor = s + + // Supervisor will manage our presentation and draw us "on top" + w.supervisor.DrawOnTop(w) +} + // SetStyle sets the tooltip's default style. func (w *Tooltip) SetStyle(v *style.Tooltip) { if v == nil { diff --git a/tooltip_test.go b/tooltip_test.go index 2f47fad..d08a693 100644 --- a/tooltip_test.go +++ b/tooltip_test.go @@ -21,10 +21,24 @@ func ExampleTooltip() { // Add a tooltip to it. The tooltip attaches itself to the button's // MouseOver, MouseOut, Compute and Present handlers -- you don't need to // place the tooltip inside the window or parent frame. - ui.NewTooltip(btn, ui.Tooltip{ + tt := ui.NewTooltip(btn, ui.Tooltip{ Text: "This is a tooltip that pops up\non mouse hover!", Edge: ui.Right, }) + // Notice: by default (with just the above code), the Tooltip will present + // when its target widget presents. For densely packed UIs, the Tooltip may + // be drawn "below" a neighboring widget, e.g. for horizontally packed buttons + // where the Tooltip is on the Right: the tooltip for the left-most button + // would present when the button does, but then the next button over will present + // and overwrite the tooltip. + // + // For many simple UIs you can arrange your widgets and tooltip edge to + // avoid this, but to guarantee the Tooltip always draws "on top", you + // need to give it your Supervisor so it can register itself into its + // Present stage (similar to window management). Be sure to call Supervisor.Present() + // lastly in your main loop. + tt.Supervise(mw.Supervisor()) + mw.MainLoop() }