How can i add a text help window and a help button into the 3D View?
Been playing around with bgl visualizations for sounds. Used a non modal approach akin to https://blender.stackexchange.com/a/40676/15543  Later on decided it would be nice to have some way to hover over and display information.  Left clicking involved having to keep tabs on the 3dcursor.

Here is a stripped down but working version of where I got to with hover.  Meant to get around to giving "buttons" a state eg mouse_over etc but... Also an alignment eg (TOP, MIDDLE, BOTTOM) (LEFT, CENTRE, RIGHT)

Uses screen area indexes to keep tabs on which area to show help screen.  Button is white when initialised, blue when mouse in area and red when hovered over.  The green help window appears when hovered over.

The modal operator is invoked with 'INVOKE_SCREEN'

Note: you can save settings to the screen datablock, which is a nicely away from prying.

import bpy
import blf
from bpy.props import IntProperty, FloatProperty
import bgl

class BGLWidget:
    handle = None

    def __init__(self, op, context, areatype):

        # Calculate scroller width, dpi and pixelsize dependent
        self.pixel_size = context.user_preferences.system.pixel_size
        self.dpi = context.user_preferences.system.dpi
        self.dpi_fac = self.pixel_size * self.dpi / 72
        # A normal widget unit is 20, but the scroller is apparently 16
        self.scroller_width = 16 * self.dpi_fac

        self.op = op
        self.areatype = areatype

        self.handle = self.create_handle(context)
        theme = context.user_preferences.themes[0]
        self.theme = theme


    def create_handle(self, context):
        handle = self.areatype.draw_handler_add(
            self.draw_region,
            (context,),
           'WINDOW', 'POST_PIXEL')  
        return handle     

    def remove_handle(self):

        if self.handle:
            self.areatype.draw_handler_remove(self.handle, 'WINDOW') 
            self.handle = None   

    def draw_region(self, context):
        # check validity

        self.visualise(context)


    def draw_box(self, x, y, w, h, color=(0.0, 0.0, 0.0, 1.0)):
        #bgl.glDepthRange (0.1, 1.0)
        bgl.glColor4f(*color)
        bgl.glBegin(bgl.GL_QUADS)

        bgl.glVertex2f(x+w, y+h)
        bgl.glVertex2f(x, y+h) 
        bgl.glVertex2f(x, y) 
        bgl.glVertex2f(x+w, y)      
        bgl.glEnd()

    def visualise(self, context):
        # used to draw override in class def
        pass

class Button:
    def __init__(self, x, y, w, h, color=(1,1,1,1)):
        #draw a box
        self.x = 0
        self.y = 0
        self.w = w
        self.h = h
        self.color = color   

    def __str__(self):
        return "Button %s" % str(self.color)

    def __repr__(self):
        return "Button %d %d color(%s)" % (self.x, self.y, str(self.color))

    def in_box(self, x, y):
        return (self.x &lt; x &lt; self.x + self.w
                and self.y &lt; y &lt; self.y + self.h)



class ButtonWidget(BGLWidget):
    help_screen = -1
    buttons = []
    screen_buttons = {}
    def button(self, w, h):
        # add a new button
        b = Button(0, 0, w, h)
        self.buttons.append(b)
        return b


    def visualise(self, context):
        if self.help_screen &gt; -1:
            print("HELP", self.help_screen)
            if context.screen.areas[self.help_screen] == context.area:
                self.draw_box(0, 0, 10000, 10000, color=(0, 1, 0, 1))
                context.area.tag_redraw()
        for b in self.buttons:
            self.draw_button(b, context)

    def draw_button(self, box, context):
        m = [i for i, a in enumerate(context.screen.areas) if a == context.area]
        if not len(m):
            return None
        key = "area%d" % m[0]
        b = self.screen_buttons.setdefault(key, Button(box.x, box.y, box.w, box.h, color=box.color))
        b.x = context.region.width - b.w - self.scroller_width
        b.y = context.region.height - b.h - self.scroller_width
        #print(b.x, b.y, b.w, b.h)  # debug shows box coords on draw
        self.draw_box(b.x, b.y, b.w, b.h, color=b.color)
        #self.screen_buttons[key] = b

    def mouse_over(self, screen, area_index, x, y):
        key = "area%d" % area_index
        box = self.screen_buttons.get(key, None)
        if box:
            area = screen.areas[area_index]
            if box.in_box(x, y):
                box.color = (1, 0, 0, 0)
                self.help_screen = area_index
                area.tag_redraw()
            else:
                self.help_screen = -1
                box.color = (0, 0, 1, 0)
            #self.screen_buttons[key] = box
            area.tag_redraw()


class ModalOperator(bpy.types.Operator):
    """Move an object with the mouse, example"""
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    def modal(self, context, event):
        def in_area(area, x, y):

            return (area.x &lt; x &lt; area.x + area.width 
                and area.y &lt; y &lt; area.y + area.height)

        screen = context.screen
        mx = event.mouse_x
        my = event.mouse_y
        #print(mx, my)        
        areas = [i for i, a in enumerate(screen.areas) if a.type.startswith('VIEW_3D')
                 and in_area(a, mx, my)]        

        for i in areas:
            a = screen.areas[i]
            region = a.regions[-1]
            x = mx - region.x
            y = my - region.y
            ui.mouse_over(screen, i, x, y)
            if event.type == 'LEFTMOUSE':

                print('PRESS in screen["%s"].areas["%d"]' % (screen.name, i))
                #click events ???

        if event.type in {'ESC'}:
            # dont have to remove the UI here

            ui.remove_handle()
            return {'CANCELLED'}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

def register():
    bpy.utils.register_class(ModalOperator)


def unregister():
    bpy.utils.unregister_class(ModalOperator)

if __name__ == "__main__":
    register()

    # create a UI
    context = bpy.context                 
    h = 50
    w = 200
    ui = ButtonWidget(None, context, bpy.types.SpaceView3D)
    button = ui.button(w, h)
    for a in context.screen.areas:
        if a.type == 'VIEW_3D':
            a.tag_redraw()
    bpy.ops.object.modal_operator('INVOKE_SCREEN')