Tooltips: how to draw on top of all widgets
By default, Tooltips will present after their associated widget presents (if the mouse cursor is hovering over that widget, and the tooltip should appear). But the tooltip is not guaranteed to draw "on top" of neighboring doodads, unless you choose your Edge carefully depending on the order you're drawing your widgets. To solve this, Tooltips can be supervised to DrawOnTop() when they're activated. To opt in, you simply call the Tooltip.Supervise() function with your supervisor.
This commit is contained in:
parent
76ddda352d
commit
c9c7b33647
|
@ -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)
|
||||
}
|
||||
|
|
29
tooltip.go
29
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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user