Page MenuHome

modal operator and dislocated button tooltip
Needs Triage, NormalPublic

Description

System Information
Operating system: macOS-10.13.6-x86_64-i386-64bit 64 Bits

Blender Version
Broken: version: 2.93.0 Alpha, branch: master, commit date: 2021-03-15 19:47, hash: rBbe51d671b500, 2.83.12 as well
Worked: ?

Short description of error
sidebar button tooltip is dislocated and does not fade out when modal operator is used with some heavier processing in background and timing is just right

Exact steps for others to reproduce the error
run simplified code and do as in video. original code uses gpu module to draw in viewport (hence tag_redraw()) and is constantly ray casting prepared bvhtree (simulated by sleep()). your timing have to be just right to get the result. i'm sorry, but it is happening very randomly. sometimes almost on each button hit, sometimes not even once.

import time
import bpy
from bpy.types import Panel, Operator


class Manager():
    _tool = None


class A_OT_op(Operator):
    bl_idname = "a.op"
    bl_label = "Op"
    bl_description = "Op"
    
    @classmethod
    def poll(cls, context, ):
        if(context.space_data.type == 'VIEW_3D'):
            if(Manager._tool is None):
                return True
        return False
    
    def _tag_redraw(self, ):
        for window in bpy.context.window_manager.windows:
            for area in window.screen.areas:
                if(area.type == 'VIEW_3D'):
                    area.tag_redraw()
    
    def _is_viewport(self, context, event, ):
        def in_region(r):
            x = r.x
            y = r.y
            w = r.width
            h = r.height
            if(mx > x and mx < x + w):
                if(my > y and my < y + h):
                    return True
            return False
        
        for a in context.screen.areas:
            if(a.type == 'VIEW_3D'):
                mx = event.mouse_x
                my = event.mouse_y
                
                for r in a.regions:
                    if(r.type in ('TOOL_HEADER', 'HEADER', 'TOOLS', 'UI', 'HUD', )):
                        if(in_region(r)):
                            return False
                    elif(r.type == 'WINDOW'):
                        if(in_region(r)):
                            return True
        return False
    
    def modal(self, context, event, ):
        if(not self._is_viewport(context, event, )):
            context.window.cursor_modal_restore()
            return {'PASS_THROUGH'}
        else:
            context.window.cursor_modal_set('PAINT_CROSS')
            self._tag_redraw()
        
        if(event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}):
            # allow navigation..
            context.window.cursor_modal_restore()
            return {'PASS_THROUGH'}
        elif(event.type == 'MOUSEMOVE'):
            
            time.sleep(0.1)
            
        elif(event.type == 'LEFTMOUSE'):
            pass
        elif(event.type in {'RIGHTMOUSE', 'ESC', }):
            context.window.cursor_modal_restore()
            self._tag_redraw()
            Manager._tool = None
            return {'CANCELLED'}
        return {'RUNNING_MODAL'}
    
    def invoke(self, context, event, ):
        time.sleep(0.1)
        
        Manager._tool = self
        context.window_manager.modal_handler_add(self)
        self._tag_redraw()
        return {'RUNNING_MODAL'}


class A_PT_main(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "A"
    bl_label = "A"
    bl_options = set()
    
    def draw(self, context):
        l = self.layout
        c = l.column()
        y = 2.0
        cc = c.column(align=True)
        
        r = cc.row(align=True)
        r.scale_y = y
        r.alert = True
        r.operator('a.op')
        
        r = cc.row(align=True)
        r.scale_y = y
        r.alert = True
        r.operator('a.op')
        
        r = cc.row(align=True)
        r.scale_y = y
        r.alert = True
        r.operator('a.op')
        
        r = cc.row(align=True)
        r.scale_y = y
        r.alert = True
        r.operator('a.op')
        
        r = cc.row(align=True)
        r.scale_y = y
        r.alert = True
        r.operator('a.op')        


classes = (
    A_OT_op,
    A_PT_main,
)


def register():
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()