Handle events in OpenGL

Hi everyone.
I need to show images inside Blender interface. I found add-on “Sun position”, who does similar what I need. It use bgl module to draw image over the UI. I wrote this code:


class Preview:

    def __init__(self, imagepath, context):
        self.image_path = imagepath
        self.load_image()
        self.handler = bpy.types.SpaceProperties.draw_handler_add(
                                self.render,
                                (context,), 'WINDOW', 'POST_PIXEL')
        self.x = 5
        self.y = 70
        self.width = 500
        self.height = 250

    def load_image(self):
        self.image = bpy.data.images.load(self.image_path)
        self.image.user_clear()

    def render(self, context):
        bgl.glEnable(bgl.GL_BLEND)
        self.image.gl_load(bgl.GL_NEAREST, bgl.GL_NEAREST)
        bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.image.bindcode)
        bgl.glTexParameteri(bgl.GL_TEXTURE_2D,
                                bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
        bgl.glTexParameteri(bgl.GL_TEXTURE_2D,
                                bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
        bgl.glEnable(bgl.GL_TEXTURE_2D)
        bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
        bgl.glBegin(bgl.GL_QUADS)
        bgl.glTexCoord2f(0.0, 0.0)
        bgl.glVertex2f(self.x, self.y)
        bgl.glTexCoord2f(1.0, 0.0)
        bgl.glVertex2f(self.x + self.width, self.y)
        bgl.glTexCoord2f(1.0, 1.0)
        bgl.glVertex2f(self.x + self.width, self.y + self.height)
        bgl.glTexCoord2f(0.0, 1.0)
        bgl.glVertex2f(self.x, self.height + self.y)
        bgl.glEnd()
        bgl.glDisable(bgl.GL_TEXTURE_2D)
        bgl.glFlush()

and it works properly.
But now I have next problem: how to handle events in bgl. I tried to understand it inside sun position code, but I can’t.
Please, explain me.

you don’t handle events in bgl, but in the modal() method of a modal operator you need to be running.

Here’s a simple example:

import bpy
import bgl
import blf

#text = "Right-click to select - got it?"
text = "RMB Select is not a bug, it's a feature."
x = 15
y = 30
pad = 5
font_id = 0  # XXX, need to find out how best to get this.
font_size = 32
blf.size(font_id, font_size, 72)
rt = list(blf.dimensions(font_id, text))
rt[0] += x + pad
rt[1] += y + pad
lb = (x - pad, y - pad*4)
verts = ((lb[0],lb[1]),(lb[0],rt[1]),(rt[0],rt[1]),(rt[0],lb[1]))

def draw_callback_px(self, context):
    ModalDrawOperator.blink = not ModalDrawOperator.blink

    # draw some text
    blf.position(font_id, x, y, 0)
    blf.size(font_id, font_size, 72)

    
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(0, 0, 0, 0.7)
    
    bgl.glBegin(bgl.GL_QUADS)
    
    bgl.glVertex2f(verts[0][0],verts[0][1])
    bgl.glVertex2f(verts[1][0],verts[1][1])
    bgl.glVertex2f(verts[2][0],verts[2][1])
    bgl.glVertex2f(verts[3][0],verts[3][1])
    
    bgl.glEnd()

    if ModalDrawOperator.blink:
        bgl.glColor4f(1,0,0,1)
        blf.draw(font_id, text)
    
    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

class ModalDrawOperator(bpy.types.Operator):
    """Draw a line with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"

    _timer = None
    blink = False
    
    def modal(self, context, event):
        #context.area.tag_redraw()

        if event.type == 'ESC' or (event.type == 'RIGHTMOUSE' and
           event.mouse_region_x < rt[0] and
           event.mouse_region_x > lb[0] and
           event.mouse_region_y > lb[1] and
           event.mouse_region_y < rt[1]):
               
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.window_manager.event_timer_remove(self._timer)
            context.area.tag_redraw()
            return {'CANCELLED'}
        
        elif event.type == 'TIMER':
            context.area.tag_redraw()

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        
        if context.area.type == 'VIEW_3D':
            
            bpy.ops.view3d.cursor3d('INVOKE_DEFAULT')
            
            # the arguments we pass the the callback
            args = (self, context)
            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
            self._timer = context.window_manager.event_timer_add(0.5, context.window)

            context.window_manager.modal_handler_add(self)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}


def register():
    bpy.utils.register_class(ModalDrawOperator)
    kmi = bpy.context.window_manager.keyconfigs['Blender'].keymaps['3D View'].keymap_items.get("view3d.cursor3d")
    if kmi is None:
        kmi = bpy.context.window_manager.keyconfigs['Blender'].keymaps['3D View'].keymap_items.get("view3d.modal_operator")
        
    kmi.idname = "view3d.modal_operator"


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

    kmi = bpy.context.window_manager.keyconfigs['Blender'].keymaps['3D View'].keymap_items.get("view3d.cursor3d")
    if kmi is None:
        kmi = bpy.context.window_manager.keyconfigs['Blender'].keymaps['3D View'].keymap_items.get("view3d.modal_operator")
        
    kmi.idname = "view3d.cursor3d"

if __name__ == "__main__":
    register()


Run in text editor, left-click 3d view, then rmb-click the flashing text