Strips, unlike ui widgets, are immediate mode rendered - there are no rectangles or retained data to grab coordinates from. You would have to compute the view2d positions yourself.
You can’t, unless you are fine with using a hack where you shadow the transform operator directly interface with the C api. See example.
This adds frame offsets to unselected frames during move operator. Tested on Blender 3.2.1 3.5.0 beta.
import bpy
import blf
# The strip start/end frame draw callback
def draw():
if not detect_strip_move_modal():
return
region = bpy.context.region
v2d = region.view2d
system = bpy.context.preferences.system
margin_x = 9.6 * system.pixel_size
# Get the view2d rectangle.
x1, y1 = v2d.region_to_view(0, 0)
x2, y2 = v2d.region_to_view(region.width, region.height)
# View-to-screen transform factors
ay = region.height / (y2 - y1)
ax = region.width / (x2 - x1)
for s in bpy.context.sequences:
# Skip selection as they draw their own frame numbers.
if s.select:
continue
frame_start = s.frame_final_start
frame_end = s.frame_final_end
rtext_width = blf.dimensions(0, str(frame_end))[0]
# Not pixel perfect aligned, but "good enough"
y = (s.channel + 0.05 + 0.09 - y1) * ay
x_left = int((frame_start - x1) * ax + margin_x)
x_right = int((frame_end - x1) * ax - margin_x - rtext_width)
blf.color(0, 1.0, 1.0, 1.0, 1.0)
blf.position(0, x_left, y, 0)
blf.draw(0, str(frame_start))
blf.position(0, x_right, y, 0)
blf.draw(0, str(frame_end))
# -------- Detect modal boilerplate start -------
from ctypes import *
# Handler type enum. Operator is 3
WM_HANDLER_TYPE_OP = 3
version = bpy.app.version
functype = type(lambda: None)
as_ptr = bpy.types.bpy_struct.as_pointer
class ListBase(Structure):
_cache = {}
_fields_ = (("first", c_void_p), ("last", c_void_p))
def __new__(cls, c_type=None):
if c_type in cls._cache: return cls._cache[c_type]
elif c_type is None: ListBase_ = cls
else:
class ListBase_(Structure):
__name__ = __qualname__ = f"ListBase{cls.__qualname__}"
_fields_ = (("first", POINTER(c_type)),
("last", POINTER(c_type)))
__iter__ = cls.__iter__
__bool__ = cls.__bool__
return cls._cache.setdefault(c_type, ListBase_)
def __iter__(self):
links_p = []
elem_n = self.first or self.last
elem_p = elem_n and elem_n.contents.prev
if elem_p:
while elem_p:
links_p.append(elem_p.contents)
elem_p = elem_p.contents.prev
yield from reversed(links_p)
while elem_n:
yield elem_n.contents
elem_n = elem_n.contents.next
def __bool__(self):
return bool(self.first or self.last)
class StructBase(Structure):
_structs = []
__annotations__ = {}
def __init_subclass__(cls):
cls._structs.append(cls)
def __new__(cls, srna=None):
if srna is None:
return super().__new__(cls)
try:
return cls.from_address(as_ptr(srna))
except AttributeError:
raise Exception("Not a StructRNA instance")
# Required
def __init__(self, *_):
pass
# source\blender\windowmanager\wm_event_system.h
class wmEventHandler(StructBase):
next: lambda: POINTER(wmEventHandler)
prev: lambda: POINTER(wmEventHandler)
type: c_int
flag: c_char
poll: c_void_p
# source\blender\makesdna\DNA_windowmanager_types.h
class wmWindow(StructBase):
next: lambda: POINTER(wmWindow)
prev: lambda: POINTER(wmWindow)
ghostwin: c_void_p
gpuctx: c_void_p
parent: lambda: POINTER(wmWindow)
scene: c_void_p
new_scene: c_void_p
view_layer_name: c_char * 64
if version >= (3, 3):
unpinned_scene: c_void_p # Scene
workspace_hook: c_void_p
global_areas: ListBase * 3 # ScrAreaMap
screen: c_void_p # bScreen (deprecated)
if version >= (2, 93):
winid: c_int
pos: c_short * 2
size: c_short * 2
windowstate: c_char
active: c_char
if version <= (2, 93):
_pad0: c_char * 4
cursor: c_short
lastcursor: c_short
modalcursor: c_short
grabcursor: c_short
addmousemove: c_char
tag_cursor_refresh: c_char
if version > (2, 93):
event_queue_check_click: c_char
event_queue_check_drag: c_char
event_queue_check_drag_handled: c_char
_pad0: c_char * 1
else:
winid: c_int
pie_event_type_lock: c_short
pie_event_type_last: c_short
eventstate: c_void_p # wmEvent
if version >= (3, 2):
event_last_handled: c_void_p # wmEvent
else:
tweak: c_void_p # wmGesture
ime_data: c_void_p
event_queue: ListBase
handlers: ListBase(wmEventHandler)
modalhandlers: ListBase(wmEventHandler)
gesture: ListBase
stereo3d_format: c_void_p
drawcalls: ListBase
cursor_keymap_status: c_void_p
class wmOperator(StructBase):
next: lambda: POINTER(wmOperator)
prev: lambda: POINTER(wmOperator)
idname: c_char * 64
# source\blender\windowmanager\wm_event_system.h
class wmEventHandler_Op(StructBase):
class context(StructBase): # Anonymous
win: lambda: POINTER(wmWindow)
area: c_void_p # ScrArea ptr
region: c_void_p # ARegion ptr
region_type: c_short
head: wmEventHandler
op: lambda: POINTER(wmOperator) # wmOperator
is_file_select: c_bool
context: context
del context
def init_structs():
for struct in StructBase._structs:
fields = []
anons = []
for key, value in struct.__annotations__.items():
if isinstance(value, functype):
value = value()
elif isinstance(value, Union):
anons.append(key)
fields.append((key, value))
if anons:
struct._anonynous_ = anons
# Base classes might not have _fields_. Don't set anything.
if fields:
struct._fields_ = fields
struct.__annotations__.clear()
StructBase._structs.clear()
ListBase._cache.clear()
# ------- Detect modal boilerplate end -------
def detect_strip_move_modal():
win = wmWindow(bpy.context.window)
for handler in win.modalhandlers:
if handler.type == WM_HANDLER_TYPE_OP:
wmh_op = wmEventHandler_Op.from_address(addressof(handler))
idname = wmh_op.op.contents.idname.decode()
if idname == "TRANSFORM_OT_seq_slide":
return True
return False
if __name__ == "__main__":
init_structs()
# The draw handler is persistent for the duration of session.
bpy.types.SpaceSequenceEditor.draw_handler_add(
draw, (), 'WINDOW', 'POST_PIXEL')