Files
blender-portable-repo/scripts/addons/RetopoFlow/addon_common/common/ui_document.py
T
2026-03-17 14:30:01 -06:00

724 lines
30 KiB
Python

'''
Copyright (C) 2023 CG Cookie
http://cgcookie.com
hello@cgcookie.com
Created by Jonathan Denning, Jonathan Williamson
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import os
import re
import sys
import math
import time
import random
import traceback
import contextlib
from math import floor, ceil
from inspect import signature
from itertools import dropwhile
import bpy
import blf
import gpu
from gpu_extras.presets import draw_texture_2d
from mathutils import Vector, Matrix
from . import gpustate
from . import ui_settings
from .gpustate import ScissorStack
from .ui_linefitter import LineFitter
from .ui_core import UI_Element
from .ui_core_preventmulticalls import UI_Core_PreventMultiCalls
from .blender import tag_redraw_all
from .ui_styling import UI_Styling, ui_defaultstylings
from .ui_core_utilities import helper_wraptext, convert_token_to_cursor
from .fsm import FSM
from .useractions import ActionHandler
from .boundvar import BoundVar
from .blender import get_view3d_area, get_view3d_region
from .debug import debugger, dprint, tprint
from .decorators import debug_test_call, blender_version_wrapper, add_cache
from .globals import Globals
from .hasher import Hasher
from .maths import Vec2D, Color, mid, Box2D, Size1D, Size2D, Point2D, RelPoint2D, Index2D, clamp, NumberUnit
from .profiler import profiler, time_it
from .utils import iter_head
from ..ext import png
from ..ext.apng import APNG
class UI_Document:
default_keymap = {
'commit': {'RET',},
'cancel': {'ESC',},
'keypress':
{c for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'} |
{'NUMPAD_%d'%i for i in range(10)} | {'NUMPAD_PERIOD','NUMPAD_MINUS','NUMPAD_PLUS','NUMPAD_SLASH','NUMPAD_ASTERIX'} |
{'ZERO', 'ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE'} |
{'PERIOD', 'MINUS', 'SPACE', 'SEMI_COLON', 'COMMA', 'QUOTE', 'ACCENT_GRAVE', 'PLUS', 'SLASH', 'BACK_SLASH', 'EQUAL', 'LEFT_BRACKET', 'RIGHT_BRACKET'},
'scroll top': {'HOME'},
'scroll bottom': {'END'},
'scroll up': {'WHEELUPMOUSE', 'PAGE_UP', 'UP_ARROW', },
'scroll down': {'WHEELDOWNMOUSE', 'PAGE_DOWN', 'DOWN_ARROW', },
'scroll': {'TRACKPADPAN'},
}
doubleclick_time = bpy.context.preferences.inputs.mouse_double_click_time / 1000 # 0.25
wheel_scroll_lines = 3 # bpy.context.preferences.inputs.wheel_scroll_lines, see https://developer.blender.org/rBbec583951d736776d2096368ef8d2b764287ac11
allow_disabled_to_blur = False
show_tooltips = True
tooltip_delay = 0.50
max_click_dist = 10 # allows mouse to travel off element and still register a click event
allow_click_time = 0.50 # allows for very fast clicking. ignore max_click_dist if time(mouseup-mousedown) is at most allow_click_time
def __init__(self):
self._context = None
self._area = None
self._exception_callbacks = []
self._ui_scale = Globals.drawing.get_dpi_mult()
self._draw_count = 0
self._draw_time = 0
self._draw_fps = 0
def add_exception_callback(self, fn):
self._exception_callbacks += [fn]
def _callback_exception_callbacks(self, e):
for fn in self._exception_callbacks:
try:
fn(e)
except Exception as e2:
print(f'UI_Document: Caught exception while calling back exception callbacks: {fn.__name__}')
print(f' original: {e}')
print(f' additional: {e2}')
debugger.print_exception()
# @profiler.function
def init(self, context, **kwargs):
self._callbacks = {
'preclean': set(),
'postclean': set(),
'postflow': set(),
'postflow once': set(),
}
self.defer_cleaning = False
self._context = context
self._area = get_view3d_area(context)
self.actions = ActionHandler(context, UI_Document.default_keymap)
self._body = UI_Element(tagName='body', document=self) # root level element
self._tooltip = UI_Element(tagName='dialog', classes='tooltip', can_hover=False, parent=self._body)
self._tooltip.is_visible = False
self._tooltip_message = None
self._tooltip_wait = None
self._tooltip_mouse = None
self._reposition_tooltip_before_draw = False
self.fsm = FSM(self, start='main')
self.ignore_hover_change = False
self._sticky_dist = 20
self._sticky_element = None # allows the mouse to drift a few pixels off before handling mouseleave
self._under_mouse = None
self._under_mousedown = None
self._under_down = None
self._focus = None
self._focus_full = False
self._last_mx = -1
self._last_my = -1
self._last_mouse = None
self._last_under_mouse = None
self._last_under_click = None
self._last_click_time = 0
self._last_sz = None
self._last_w = -1
self._last_h = -1
def update_callbacks(self, ui_element, force_remove=False):
for cb,fn in [('preclean', ui_element.preclean), ('postclean', ui_element.postclean), ('postflow', ui_element.postflow)]:
if force_remove or not fn:
self._callbacks[cb].discard(ui_element)
else:
self._callbacks[cb].add(ui_element)
@property
def body(self):
return self._body
@property
def activeElement(self):
return self._focus
def center_on_mouse(self, element):
# centers element under mouse, must be done between first and second layout calls
if element is None: return
def center():
element._relative_pos = None
mx, my = self.actions.mouse if self.actions.mouse else (10, 10)
# w,h = element.width_pixels,element.height_pixels
w, h = element.width_pixels, element._dynamic_full_size.height
l = mx-w/2
t = -self._body.height_pixels + my + h/2
element.reposition(left=l, top=t)
self._callbacks['postflow once'].add(center)
def _reposition_tooltip(self, force=False):
if self._tooltip_mouse == self.actions.mouse and not force: return
self._tooltip_mouse = self.actions.mouse
if self._tooltip.width_pixels is None or type(self._tooltip.width_pixels) is str or self._tooltip._mbp_width is None or self._tooltip.height_pixels is None or type(self._tooltip.height_pixels) is str or self._tooltip._mbp_height is None:
ttl,ttt = self.actions.mouse
else:
ttl = self.actions.mouse.x if self.actions.mouse.x < self._body.width_pixels/2 else self.actions.mouse.x - (self._tooltip.width_pixels + (self._tooltip._mbp_width or 0))
ttt = self.actions.mouse.y if self.actions.mouse.y > self._body.height_pixels/2 else self.actions.mouse.y + (self._tooltip.height_pixels + (self._tooltip._mbp_height or 0))
hp = self._body.height_pixels if type(self._body.height_pixels) is not str else 0.0
self._tooltip.reposition(left=ttl, top=ttt - hp)
def removed_element(self, ui_element):
if self._under_mouse and self._under_mouse.is_descendant_of(ui_element):
self._under_mouse = None
if self._under_mousedown and self._under_mousedown.is_descendant_of(ui_element):
self._under_mousedown = None
if self._focus and self._focus.is_descendant_of(ui_element):
self._focus = None
def force_dirty_all(self):
self._body.dirty(children=True)
self._body.dirty_styling()
self._body.dirty_flow()
tag_redraw_all('Force Dirty All')
# @profiler.function
def update(self, context, event):
self._context = context
self._area = get_view3d_area(context)
# if context.area != self._area: return
# self._ui_scale = Globals.drawing.get_dpi_mult()
UI_Core_PreventMultiCalls.reset_multicalls()
region = get_view3d_region(context)
w,h = region.width, region.height
if self._last_w != w or self._last_h != h:
# print('Document:', (self._last_w, self._last_h), (w,h))
self._last_w,self._last_h = w,h
self._body.dirty(cause='changed document size', children=True)
self._body.dirty_flow()
tag_redraw_all("UI_Element update: w,h change")
if ui_settings.DEBUG_COLOR_CLEAN: tag_redraw_all("UI_Element DEBUG_COLOR_CLEAN")
#self.actions.update(context, event, self._timer, print_actions=False)
# self.actions.update(context, event, print_actions=False)
if self._sticky_element and not self._sticky_element.is_visible:
self._sticky_element = None
self._mx,self._my = self.actions.mouse if self.actions.mouse else (-1,-1)
if not self.ignore_hover_change:
self._under_mouse = self._body.get_under_mouse(self.actions.mouse)
if self._sticky_element:
if self._sticky_element.get_mouse_distance(self.actions.mouse) < self._sticky_dist * self._ui_scale:
if self._under_mouse is None or not self._under_mouse.is_descendant_of(self._sticky_element):
self._under_mouse = self._sticky_element
next_message = None
if self._under_mouse and self._under_mouse.title_with_for(): # and not self._under_mouse.disabled:
next_message = self._under_mouse.title_with_for()
if self._under_mouse.disabled:
next_message = f'(Disabled) {next_message}'
if self._tooltip_message != next_message:
self._tooltip_message = next_message
self._tooltip_mouse = None
self._tooltip_wait = time.time() + self.tooltip_delay
self._tooltip.is_visible = False
if self._tooltip_message and time.time() > self._tooltip_wait:
if self._tooltip_mouse != self.actions.mouse or self._tooltip.innerText != self._tooltip_message or not self._tooltip.is_visible:
# TODO: markdown support??
self._tooltip.innerText = self._tooltip_message
self._tooltip.is_visible = True and self.show_tooltips
self._reposition_tooltip_before_draw = True
tag_redraw_all("reposition tooltip")
self.fsm.update()
self._last_mx = self._mx
self._last_my = self._my
self._last_mouse = self.actions.mouse
if not self.ignore_hover_change: self._last_under_mouse = self._under_mouse
uictrld = False
uictrld |= self._under_mouse is not None and self._under_mouse != self._body
uictrld |= self.fsm.state != 'main'
uictrld |= self._focus_full
# uictrld |= self._focus is not None
return {'hover'} if uictrld else None
def _addrem_pseudoclass(self, pseudoclass, remove_from=None, add_to=None):
rem = remove_from.get_pathToRoot() if remove_from else []
add = add_to.get_pathToRoot() if add_to else []
rem.reverse()
add.reverse()
roots = []
if rem: roots.append(rem[0])
if add: roots.append(add[0])
while rem and add and rem[0] == add[0]:
rem = rem[1:]
add = add[1:]
# print(f'addrem_pseudoclass: {pseudoclass} {rem} {add}')
self.defer_cleaning = True
for root in roots: root.defer_dirty_propagation = True
for e in rem: e.del_pseudoclass(pseudoclass)
for e in add: e.add_pseudoclass(pseudoclass)
for root in roots: root.defer_dirty_propagation = False
self.defer_cleaning = False
def debug_print(self):
print('')
print('UI_Document.debug_print')
self._body.debug_print(0, set())
def debug_print_toroot(self, fromHovered=True, fromFocused=False):
print('')
print('UI_Document.debug_print_toroot')
if fromHovered: self._debug_print(self._under_mouse)
if fromFocused: self._debug_print(self._focus)
def _debug_print(self, ui_from):
# debug print!
path = ui_from.get_pathToRoot()
for i,ui_elem in enumerate(reversed(path)):
def tprint(*args, extra=0, **kwargs):
print(' '*(i+extra), end='')
print(*args, **kwargs)
tprint(str(ui_elem))
tprint(f'selector={ui_elem._selector}', extra=1)
tprint(f'l={ui_elem._l} t={ui_elem._t} w={ui_elem._w} h={ui_elem._h}', extra=1)
@property
def sticky_element(self):
return self._sticky_element
@sticky_element.setter
def sticky_element(self, element):
self._sticky_element = element
def clear_last_under(self):
self._last_under_mouse = None
def handle_hover(self, change_cursor=True):
# handle :hover, on_mouseenter, on_mouseleave
if self.ignore_hover_change: return
if change_cursor and self._under_mouse and self._under_mouse._tagName != 'body':
cursor = self._under_mouse._computed_styles.get('cursor', 'default')
Globals.cursors.set(convert_token_to_cursor(cursor))
if self._under_mouse == self._last_under_mouse: return
if self._under_mouse and not self._under_mouse.can_hover: return
self._addrem_pseudoclass('hover', remove_from=self._last_under_mouse, add_to=self._under_mouse)
if self._last_under_mouse: self._last_under_mouse.dispatch_event('on_mouseleave')
if self._under_mouse: self._under_mouse.dispatch_event('on_mouseenter')
def handle_mousemove(self, ui_element=None):
ui_element = ui_element or self._under_mouse
if ui_element is None: return
if self._last_mouse == self.actions.mouse: return
ui_element.dispatch_event('on_mousemove')
def handle_keypress(self, ui_element=None):
ui_element = ui_element or self._focus
if self.actions.pressed('clipboard paste') and ui_element:
ui_element.dispatch_event('on_paste', clipboardData=bpy.context.window_manager.clipboard)
pressed = self.actions.as_char(self.actions.just_pressed)
if pressed and ui_element:
ui_element.dispatch_event('on_keypress', key=pressed)
@FSM.on_state('main', 'enter')
def modal_main_enter(self):
Globals.cursors.set('DEFAULT')
@FSM.on_state('main')
def modal_main(self):
# print('UI_Document.main', self.actions.event_type, time.time())
if self.actions.just_pressed:
pressed = self.actions.just_pressed
if pressed not in {'WINDOW_DEACTIVATE'}:
if self._focus and self._focus_full:
self._focus.dispatch_event('on_keypress', key=pressed)
elif self._under_mouse:
self._under_mouse.dispatch_event('on_keypress', key=pressed)
self.handle_hover()
self.handle_mousemove()
if self.actions.pressed('MIDDEMOUSE'):
return 'scroll'
if self.actions.pressed('LEFTMOUSE', unpress=False, ignoremods=True, ignoremulti=True):
if self._under_mouse == self._body:
# clicking body always blurs focus
self.blur()
elif UI_Document.allow_disabled_to_blur and self._under_mouse and self._under_mouse.is_disabled:
# user clicked on disabled element, so blur current focused element
self.blur()
return 'mousedown'
if self.actions.pressed('SHIFT+F10'):
profiler.clear()
return
if self.actions.pressed('SHIFT+F11'):
profiler.printout()
self.debug_print()
return
if self.actions.pressed('CTRL+SHIFT+F11'):
self.debug_print_toroot()
print(f'{self._under_mouse._computed_styles}')
return
# if self.actions.pressed('RIGHTMOUSE') and self._under_mouse:
# self._debug_print(self._under_mouse)
# #print('focus:', self._focus)
if self.actions.pressed({'scroll top', 'scroll bottom'}, unpress=False):
move = 100000 * (-1 if self.actions.pressed({'scroll top'}) else 1)
self.actions.unpress()
if self._get_scrollable():
self._scroll_element.scrollTop = self._scroll_last.y + move
self._scroll_element._setup_ltwh(recurse_children=False)
if self.actions.pressed({'scroll', 'scroll up', 'scroll down'}, unpress=False):
if self.actions.event_type == 'TRACKPADPAN':
move = self.actions.scroll[1] # self.actions.mouse.y - self.actions.mouse_prev.y
# print(f'UI_Document.update: trackpad pan {move}')
else:
d = self.wheel_scroll_lines * 8 * Globals.drawing.get_dpi_mult()
move = Globals.drawing.scale(d) * (-1 if self.actions.pressed({'scroll up'}) else 1)
self.actions.unpress()
if self._get_scrollable():
self._scroll_element.scrollTop = self._scroll_last.y + move
self._scroll_element._setup_ltwh(recurse_children=False)
# if self.actions.pressed('F8') and self._under_mouse:
# print('\n\n')
# for e in self._under_mouse.get_pathFromRoot():
# print(e)
# print(e._dirty_causes)
# for s in e._debug_list:
# print(f' {s}')
if False:
print('---------------------------')
if self._focus: print('FOCUS', self._focus, self._focus.pseudoclasses)
else: print('FOCUS', None)
if self._under_down: print('DOWN', self._under_down, self._under_down.pseudoclasses)
else: print('DOWN', None)
if under_mouse: print('UNDER', under_mouse, under_mouse.pseudoclasses)
else: print('UNDER', None)
def _get_scrollable(self):
# find first along root to path that can scroll
if not self._under_mouse: return None
self._scroll_element = next((e for e in self._under_mouse.get_pathToRoot() if e.is_scrollable_y), None)
if self._scroll_element:
self._scroll_last = RelPoint2D((self._scroll_element.scrollLeft, self._scroll_element.scrollTop))
return self._scroll_element
@FSM.on_state('scroll', 'can enter')
def scroll_canenter(self):
if not self._get_scrollable(): return False
@FSM.on_state('scroll', 'enter')
def scroll_enter(self):
self._scroll_point = self.actions.mouse
self.ignore_hover_change = True
Globals.cursors.set('SCROLL_Y')
@FSM.on_state('scroll')
def scroll_main(self):
if self.actions.released('MIDDLEMOUSE', ignoremods=True, ignoremulti=True):
# done scrolling
return 'main'
nx = self._scroll_element.scrollLeft + (self._scroll_point.x - self._mx)
ny = self._scroll_element.scrollTop - (self._scroll_point.y - self._my)
self._scroll_element.scrollLeft = nx
self._scroll_element.scrollTop = ny
self._scroll_point = self.actions.mouse
self._scroll_element._setup_ltwh(recurse_children=False)
@FSM.on_state('scroll', 'exit')
def scroll_exit(self):
self.ignore_hover_change = False
@FSM.on_state('mousedown', 'can enter')
def mousedown_canenter(self):
return self._focus or (
self._under_mouse and self._under_mouse != self._body and not self._under_mouse.is_disabled
)
@FSM.on_state('mousedown', 'enter')
def mousedown_enter(self):
self._mousedown_time = time.time()
self._under_mousedown = self._under_mouse
if not self._under_mousedown:
# likely, self._under_mouse or an ancestor was deleted?
# mousedown main event handler below will switch FSM back to main, effectively ignoring the mousedown event
# see RetopoFlow issue #857
self.blur()
return
self._addrem_pseudoclass('active', add_to=self._under_mousedown)
self._under_mousedown.dispatch_event('on_mousedown')
# print(self._under_mouse.get_pathToRoot())
change_focus = self._focus != self._under_mouse
if change_focus:
if self._under_mouse.can_focus:
# element under mouse takes focus (or whichever it's for points to)
if self._under_mouse.forId:
f = self._under_mouse.get_for_element()
if f and f.can_focus: self.focus(f)
else: self.focus(self._under_mouse)
else: self.focus(self._under_mouse)
elif self._focus and self._is_ancestor(self._focus, self._under_mouse):
# current focus is an ancestor of new element, so don't blur!
pass
else:
self.blur()
@FSM.on_state('mousedown')
def mousedown_main(self):
if not self._under_mousedown:
return 'main'
if self.actions.released('LEFTMOUSE', ignoremods=True, ignoremulti=True):
# done with mousedown
return 'focus' if self._under_mousedown.can_focus else 'main'
if self.actions.pressed('RIGHTMOUSE', ignoremods=True, unpress=False):
self._under_mousedown.dispatch_event('on_mousedown')
self.handle_hover(change_cursor=False)
self.handle_mousemove(ui_element=self._under_mousedown)
self.handle_keypress(ui_element=self._under_mousedown)
@FSM.on_state('mousedown', 'exit')
def mousedown_exit(self):
if not self._under_mousedown:
# likely, self._under_mousedown or an ancestor was deleted while under mousedown
# need to reset variables enough to get us back to main FSM state!
self._last_under_click = None
self._last_click_time = 0
self.ignore_hover_change = False
return
self._under_mousedown.dispatch_event('on_mouseup')
under_mouseclick = self._under_mousedown
click = False
click |= time.time() - self._mousedown_time < self.allow_click_time
click |= self._under_mousedown.get_mouse_distance(self.actions.mouse) <= self.max_click_dist * self._ui_scale
if not click:
# find closest common ancestor of self._under_mouse and self._under_mousedown that is getting clicked
ancestors0 = self._under_mousedown.get_pathFromRoot()
ancestors1 = self._under_mouse.get_pathFromRoot() if self._under_mouse else []
ancestors = [a0 for (a0, a1) in zip(ancestors0, ancestors1) if a0 == a1 and a0.get_mouse_distance(self.actions.mouse) < 1]
if ancestors:
under_mouseclick = ancestors[-1]
click = True
# print('mousedown_exit', time.time()-self._mousedown_time, self.allow_click_time, self.actions.mouse, self._under_mousedown.get_mouse_distance(self.actions.mouse), self.max_click_dist)
if click:
# old/simple: self._under_mouse == self._under_mousedown:
dblclick = True
dblclick &= under_mouseclick == self._last_under_click
dblclick &= time.time() < self._last_click_time + self.doubleclick_time
under_mouseclick.dispatch_event('on_mouseclick')
self._last_under_click = under_mouseclick
if dblclick:
under_mouseclick.dispatch_event('on_mousedblclick')
# self._last_under_click = None
# if self._under_mousedown:
# # if applicable, send mouseclick events to ui_element indicated by forId
# ui_for = self._under_mousedown.get_for_element()
# print(f'mousedown_exit:')
# print(f' ui under: {self._under_mousedown}')
# print(f' ui for: {ui_for}')
# if ui_for: ui_for.dispatch_event('on_mouseclick')
self._last_click_time = time.time()
else:
self._last_under_click = None
self._last_click_time = 0
self._addrem_pseudoclass('active', remove_from=self._under_mousedown)
# self._under_mousedown.del_pseudoclass('active')
def _is_ancestor(self, ancestor, descendant):
return ancestor in descendant.get_pathToRoot()
def blur(self, stop_at=None):
self._focus_full = False
if self._focus is None: return
self._focus.del_pseudoclass('focus')
self._focus.dispatch_event('on_blur')
self._focus.dispatch_event('on_focusout', stop_at=stop_at)
self._addrem_pseudoclass('active', remove_from=self._focus)
self._focus = None
def focus(self, ui_element, full=False):
if ui_element is None: return
if self._focus == ui_element: return
stop_focus_at = None
if self._focus:
stop_blur_at = None
p_focus = ui_element.get_pathFromRoot()
p_blur = self._focus.get_pathFromRoot()
for i in range(min(len(p_focus), len(p_blur))):
if p_focus[i] != p_blur[i]:
stop_focus_at = p_focus[i]
stop_blur_at = p_blur[i]
break
self.blur(stop_at=stop_blur_at)
#print('focusout to', p_blur, stop_blur_at)
#print('focusin from', p_focus, stop_focus_at)
self._focus_full = full
self._focus = ui_element
self._focus.add_pseudoclass('focus')
self._focus.dispatch_event('on_focus')
self._focus.dispatch_event('on_focusin', stop_at=stop_focus_at)
@FSM.on_state('focus')
def focus_main(self):
if not self._focus:
return 'main'
if self._focus_full:
pass
if self.actions.pressed('LEFTMOUSE', unpress=False):
return 'mousedown'
# if self.actions.pressed('RIGHTMOUSE'):
# self._debug_print(self._focus)
# if self.actions.pressed('ESC'):
# self.blur()
# return 'main'
self.handle_hover()
self.handle_mousemove()
self.handle_keypress()
if not self._focus: return 'main'
def force_clean(self, context):
if self.defer_cleaning: return
time_start = time.time()
region = get_view3d_region(context)
w,h = region.width, region.height
sz = Size2D(width=w, max_width=w, height=h, max_height=h)
UI_Core_PreventMultiCalls.reset_multicalls()
Globals.ui_draw.update()
if Globals.drawing.get_dpi_mult() != self._ui_scale:
print(f'DPI CHANGED: {self._ui_scale} -> {Globals.drawing.get_dpi_mult()}')
self._ui_scale = Globals.drawing.get_dpi_mult()
self._body.dirty(cause='DPI changed', children=True)
self._body.dirty_styling()
self._body.dirty_flow(children=True)
if (w,h) != self._last_sz:
self._last_sz = (w,h)
self._body.dirty_flow()
# self._body.dirty('region size changed', 'style', children=True)
# UI_Core_PreventMultiCalls.reset_multicalls()
for o in self._callbacks['preclean']: o._call_preclean()
self._body.clean()
for o in self._callbacks['postclean']: o._call_postclean()
self._body._layout(
# linefitter=LineFitter(left=0, top=h-1, width=w, height=h),
fitting_size=sz,
fitting_pos=Point2D((0,h-1)),
parent_size=sz,
nonstatic_elem=self._body,
table_data={},
)
self._body.set_view_size(sz)
for o in self._callbacks['postflow']: o._call_postflow()
for fn in self._callbacks['postflow once']: fn()
self._callbacks['postflow once'].clear()
# UI_Core_PreventMultiCalls.reset_multicalls()
self._body._layout(
# linefitter=LineFitter(left=0, top=h-1, width=w, height=h),
fitting_size=sz,
fitting_pos=Point2D((0,h-1)),
parent_size=sz,
nonstatic_elem=self._body,
table_data={},
)
self._body.set_view_size(sz)
if self._reposition_tooltip_before_draw:
self._reposition_tooltip_before_draw = False
self._reposition_tooltip()
# @profiler.function
def draw(self, context):
self._context = context
self._area = get_view3d_area(context)
# if self._area != context.area: return
Globals.drawing.glCheckError('UI_Document.draw: start')
time_start = time.time()
self.force_clean(context)
Globals.drawing.glCheckError('UI_Document.draw: setting options')
ScissorStack.start(context)
gpustate.blend('ALPHA')
gpustate.scissor_test(True)
gpustate.depth_test('NONE')
Globals.drawing.glCheckError('UI_Document.draw: drawing')
self._body.draw()
ScissorStack.end()
self._draw_count += 1
self._draw_time += time.time() - time_start
if self._draw_count % 100 == 0:
fps = (self._draw_count / self._draw_time) if self._draw_time>0 else float('inf')
self._draw_fps = fps
# print('~%f fps (%f / %d = %f)' % (self._draw_fps, self._draw_time, self._draw_count, self._draw_time / self._draw_count))
self._draw_count = 0
self._draw_time = 0
Globals.drawing.glCheckError('UI_Document.draw: done')
ui_document = Globals.set(UI_Document())