2025-07-01
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
bl_info = {
|
||||
"name": "BL UI Widgets",
|
||||
"description": "UI Widgets to draw in the 3D view",
|
||||
"author": "Jayanam",
|
||||
"version": (0, 6, 4, 2),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "View3D",
|
||||
"category": "Object",
|
||||
}
|
||||
|
||||
# Blender imports
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(DP_OT_draw_operator)
|
||||
kcfg = bpy.context.window_manager.keyconfigs.addon
|
||||
if kcfg:
|
||||
km = kcfg.keymaps.new(name="3D View", space_type="VIEW_3D")
|
||||
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
def unregister():
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
|
||||
bpy.utils.unregister_class(DP_OT_draw_operator)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -0,0 +1,230 @@
|
||||
import os
|
||||
|
||||
import blf
|
||||
import bpy
|
||||
import gpu
|
||||
|
||||
from .. import image_utils, ui_bgl
|
||||
from .bl_ui_widget import BL_UI_Widget
|
||||
|
||||
|
||||
class BL_UI_Button(BL_UI_Widget):
|
||||
def __init__(self, x, y, width, height):
|
||||
super().__init__(x, y, width, height)
|
||||
self._text_color = (1.0, 1.0, 1.0, 1.0)
|
||||
self._hover_bg_color = (0.5, 0.5, 0.5, 1.0)
|
||||
self._select_bg_color = (0.7, 0.7, 0.7, 1.0)
|
||||
|
||||
self._text = "Button"
|
||||
self._text_size = 16
|
||||
self._textpos = (x, y)
|
||||
|
||||
self.__state = 0
|
||||
self.__image = None
|
||||
self.__image_size = (24, 24)
|
||||
self.__image_position = (4, 2)
|
||||
|
||||
@property
|
||||
def text_color(self):
|
||||
return self._text_color
|
||||
|
||||
@text_color.setter
|
||||
def text_color(self, value):
|
||||
if value != self._text_color:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._text_color = value
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
if value != self._text:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._text = value
|
||||
|
||||
@property
|
||||
def text_size(self):
|
||||
return self._text_size
|
||||
|
||||
@text_size.setter
|
||||
def text_size(self, value):
|
||||
if value != self._text_size:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._text_size = value
|
||||
|
||||
@property
|
||||
def hover_bg_color(self):
|
||||
return self._hover_bg_color
|
||||
|
||||
@hover_bg_color.setter
|
||||
def hover_bg_color(self, value):
|
||||
if value != self._hover_bg_color:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._hover_bg_color = value
|
||||
|
||||
@property
|
||||
def select_bg_color(self):
|
||||
return self._select_bg_color
|
||||
|
||||
@select_bg_color.setter
|
||||
def select_bg_color(self, value):
|
||||
if value != self._select_bg_color:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._select_bg_color = value
|
||||
|
||||
def set_image_size(self, image_size):
|
||||
self.__image_size = image_size
|
||||
|
||||
def set_image_position(self, image_position):
|
||||
self.__image_position = image_position
|
||||
|
||||
def check_image_exists(self):
|
||||
# it's possible image was removed and doesn't exist.
|
||||
try:
|
||||
self.__image
|
||||
self.__image.filepath
|
||||
# self.__image.pixels
|
||||
except Exception as e:
|
||||
self.__image = None
|
||||
|
||||
def set_image_colorspace(self, colorspace):
|
||||
image_utils.set_colorspace(self.__image, colorspace)
|
||||
|
||||
def set_image(self, rel_filepath):
|
||||
# first try to access the image, for cases where it can get removed
|
||||
self.check_image_exists()
|
||||
try:
|
||||
if self.__image is None or self.__image.filepath != rel_filepath:
|
||||
imgname = f".{os.path.basename(rel_filepath)}"
|
||||
img = bpy.data.images.get(imgname)
|
||||
if img is not None:
|
||||
self.__image = img
|
||||
else:
|
||||
self.__image = bpy.data.images.load(
|
||||
rel_filepath, check_existing=True
|
||||
)
|
||||
self.__image.name = imgname
|
||||
|
||||
self.__image.gl_load()
|
||||
|
||||
if self.__image and len(self.__image.pixels) == 0:
|
||||
self.__image.reload()
|
||||
self.__image.gl_load()
|
||||
except Exception as e:
|
||||
print(f"BL_UI_BUTTON set_image() error: {e}")
|
||||
self.__image = None
|
||||
|
||||
def get_image_path(self):
|
||||
self.check_image_exists()
|
||||
if self.__image is None:
|
||||
return None
|
||||
return self.__image.filepath
|
||||
|
||||
def update(self, x, y):
|
||||
super().update(x, y)
|
||||
self._textpos = [x, y]
|
||||
|
||||
def draw(self):
|
||||
if not self._is_visible:
|
||||
return
|
||||
area_height = self.get_area_height()
|
||||
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.shader.bind()
|
||||
|
||||
self.set_colors()
|
||||
|
||||
self.batch_panel.draw(self.shader)
|
||||
|
||||
self.draw_image()
|
||||
|
||||
# Draw text
|
||||
self.draw_text(area_height)
|
||||
|
||||
def set_colors(self):
|
||||
color = self._bg_color
|
||||
|
||||
# pressed
|
||||
if self.__state == 1:
|
||||
color = self._select_bg_color
|
||||
|
||||
# hover
|
||||
elif self.__state == 2:
|
||||
color = self._hover_bg_color
|
||||
|
||||
self.shader.uniform_float("color", color)
|
||||
|
||||
def draw_text(self, area_height):
|
||||
font_id = 1
|
||||
if bpy.app.version < (3, 1, 0):
|
||||
# Blender 3.0 requires size:int https://docs.blender.org/api/3.0/blf.html#blf.size
|
||||
# but assetBar's search tab text is float - needs conversion in here
|
||||
blf.size(font_id, int(self._text_size), 72)
|
||||
elif bpy.app.version < (4, 0, 0):
|
||||
blf.size(font_id, self._text_size, 72)
|
||||
else:
|
||||
blf.size(font_id, self._text_size)
|
||||
|
||||
size = blf.dimensions(font_id, self._text)
|
||||
|
||||
textpos_y = area_height - self._textpos[1] - (self.height + size[1]) / 2.0
|
||||
blf.position(
|
||||
font_id, self._textpos[0] + (self.width - size[0]) / 2.0, textpos_y + 1, 0
|
||||
)
|
||||
|
||||
r, g, b, a = self._text_color
|
||||
blf.color(font_id, r, g, b, a)
|
||||
|
||||
blf.draw(font_id, self._text)
|
||||
|
||||
def draw_image(self):
|
||||
if self.__image is not None:
|
||||
y_screen_flip = self.get_area_height() - self.y_screen
|
||||
off_x, off_y = self.__image_position
|
||||
sx, sy = self.__image_size
|
||||
ui_bgl.draw_image(
|
||||
self.x_screen + off_x,
|
||||
y_screen_flip - off_y - sy,
|
||||
sx,
|
||||
sy,
|
||||
self.__image,
|
||||
1.0,
|
||||
crop=(0, 0, 1, 1),
|
||||
batch=None,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_mouse_down(self, mouse_down_func):
|
||||
self.mouse_down_func = mouse_down_func
|
||||
|
||||
def mouse_down(self, x, y):
|
||||
if self.is_in_rect(x, y):
|
||||
self.__state = 1
|
||||
try:
|
||||
self.mouse_down_func(self)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def mouse_move(self, x, y):
|
||||
if self.is_in_rect(x, y):
|
||||
if self.__state != 1:
|
||||
# hover state
|
||||
self.__state = 2
|
||||
else:
|
||||
self.__state = 0
|
||||
|
||||
def mouse_up(self, x, y):
|
||||
if self.is_in_rect(x, y):
|
||||
self.__state = 2
|
||||
else:
|
||||
self.__state = 0
|
||||
@@ -0,0 +1,58 @@
|
||||
from .bl_ui_widget import BL_UI_Widget
|
||||
|
||||
|
||||
class BL_UI_Drag_Panel(BL_UI_Widget):
|
||||
def __init__(self, x, y, width, height):
|
||||
super().__init__(x, y, width, height)
|
||||
self.drag_offset_x = 0
|
||||
self.drag_offset_y = 0
|
||||
self.is_drag = False
|
||||
self.widgets = []
|
||||
|
||||
def set_location(self, x, y):
|
||||
super().set_location(x, y)
|
||||
self.layout_widgets()
|
||||
|
||||
def add_widget(self, widget):
|
||||
self.widgets.append(widget)
|
||||
|
||||
def add_widgets(self, widgets):
|
||||
self.widgets = widgets
|
||||
self.layout_widgets()
|
||||
|
||||
def layout_widgets(self):
|
||||
for widget in self.widgets:
|
||||
widget.update(self.x_screen + widget.x, self.y_screen + widget.y)
|
||||
|
||||
def update(self, x, y):
|
||||
super().update(x - self.drag_offset_x, y + self.drag_offset_y)
|
||||
|
||||
def child_widget_focused(self, x, y):
|
||||
for widget in self.widgets:
|
||||
if widget.is_in_rect(x, y):
|
||||
return True
|
||||
return False
|
||||
|
||||
def mouse_down(self, x, y):
|
||||
if self.child_widget_focused(x, y):
|
||||
return False
|
||||
|
||||
if self.is_in_rect(x, y):
|
||||
height = self.get_area_height()
|
||||
self.is_drag = True
|
||||
self.drag_offset_x = x - self.x_screen
|
||||
self.drag_offset_y = y - (height - self.y_screen)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def mouse_move(self, x, y):
|
||||
if self.is_drag:
|
||||
height = self.get_area_height()
|
||||
self.update(x, height - y)
|
||||
self.layout_widgets()
|
||||
|
||||
def mouse_up(self, x, y):
|
||||
self.is_drag = False
|
||||
self.drag_offset_x = 0
|
||||
self.drag_offset_y = 0
|
||||
@@ -0,0 +1,117 @@
|
||||
import traceback
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class BL_UI_OT_draw_operator(Operator):
|
||||
bl_idname = "object.bl_ui_ot_draw_operator"
|
||||
bl_label = "bl ui widgets operator"
|
||||
bl_description = "Operator for bl ui widgets"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.draw_handle = None
|
||||
self.draw_event = None
|
||||
self._finished = False
|
||||
|
||||
self.widgets = []
|
||||
self._timer_interval = 0.1
|
||||
|
||||
def init_widgets(self, context, widgets):
|
||||
self.widgets = widgets
|
||||
for widget in self.widgets:
|
||||
widget.init(context)
|
||||
|
||||
def on_invoke(self, context, event):
|
||||
pass
|
||||
|
||||
def on_finish(self, context):
|
||||
self._finished = True
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.on_invoke(context, event)
|
||||
|
||||
args = (self, context)
|
||||
|
||||
self.register_handlers(args, context, timer_interval=self._timer_interval)
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
|
||||
# first set pointers to keep track if the area is still available
|
||||
self.active_window_pointer = context.window.as_pointer()
|
||||
self.active_area_pointer = context.area.as_pointer()
|
||||
self.active_region_pointer = context.region.as_pointer()
|
||||
|
||||
context.region.tag_redraw()
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def register_handlers(self, args, context, timer_interval=0.1):
|
||||
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(
|
||||
self.draw_callback_px, args, "WINDOW", "POST_PIXEL"
|
||||
)
|
||||
self.draw_event = context.window_manager.event_timer_add(
|
||||
timer_interval, window=context.window
|
||||
)
|
||||
|
||||
def unregister_handlers(self, context):
|
||||
context.window_manager.event_timer_remove(self.draw_event)
|
||||
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")
|
||||
|
||||
self.draw_handle = None
|
||||
self.draw_event = None
|
||||
|
||||
def handle_widget_events(self, event):
|
||||
result = False
|
||||
# we iterate widgets reversed, so top buttons can get processed first if buttons overlap.
|
||||
for widget in reversed(self.widgets):
|
||||
if widget.handle_event(event):
|
||||
result = True
|
||||
return True # return prematurely to avoid conflicts.
|
||||
return result
|
||||
|
||||
def modal(self, context, event):
|
||||
if self._finished:
|
||||
return {"FINISHED"}
|
||||
|
||||
if context.area:
|
||||
context.region.tag_redraw()
|
||||
|
||||
if self.handle_widget_events(event):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
if event.type in {"ESC"}:
|
||||
self.finish()
|
||||
|
||||
return {"PASS_THROUGH"}
|
||||
|
||||
def finish(self):
|
||||
self.unregister_handlers(bpy.context)
|
||||
# it is possible that the area has been closed, so we check if it is still available
|
||||
if bpy.context.region is not None:
|
||||
bpy.context.region.tag_redraw()
|
||||
self.on_finish(bpy.context)
|
||||
|
||||
# Draw handler to paint onto the screen
|
||||
def draw_callback_px(self, op, context):
|
||||
draw_callback_px_separated(self, op, context)
|
||||
|
||||
def cancel(self, context):
|
||||
"""Cancel the modal operator and finish. This is called before unregistration on Blender quit. Has to be here, so BL_UI_Button, BL_UI_Drag_Panel, BL_UI_Image and other elements are removed with finish().
|
||||
We cannot call this during unregister because at that stage Operator is already removed, but BL_UI_Button is kept in memory causing memory leaks. Issue: #770
|
||||
"""
|
||||
self.finish()
|
||||
|
||||
|
||||
def draw_callback_px_separated(self, op, context):
|
||||
# separated only for puprpose of profiling
|
||||
try:
|
||||
# hide during animation playback, to improve performance
|
||||
if context.screen.is_animation_playing:
|
||||
return
|
||||
if context.area.as_pointer() == self.active_area_pointer:
|
||||
for widget in self.widgets:
|
||||
widget.draw()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
|
||||
import bpy
|
||||
|
||||
from .. import image_utils, ui_bgl
|
||||
from .bl_ui_widget import BL_UI_Widget
|
||||
|
||||
|
||||
class BL_UI_Image(BL_UI_Widget):
|
||||
def __init__(self, x, y, width, height):
|
||||
super().__init__(x, y, width, height)
|
||||
|
||||
self.__state = 0
|
||||
self.__image = None
|
||||
self.__image_size = (24, 24)
|
||||
self.__image_position = (4, 2)
|
||||
|
||||
def set_image_size(self, image_size):
|
||||
self.__image_size = image_size
|
||||
|
||||
def set_image_position(self, image_position):
|
||||
self.__image_position = image_position
|
||||
|
||||
def check_image_exists(self):
|
||||
# it's possible image was removed and doesn't exist.
|
||||
try:
|
||||
self.__image
|
||||
self.__image.filepath
|
||||
except Exception as e:
|
||||
self.__image = None
|
||||
return None
|
||||
|
||||
def set_image(self, rel_filepath):
|
||||
# first try to access the image, for cases where it can get removed
|
||||
self.check_image_exists()
|
||||
try:
|
||||
if self.__image is None or self.__image.filepath != rel_filepath:
|
||||
imgname = f".{os.path.basename(rel_filepath)}"
|
||||
img = bpy.data.images.get(imgname)
|
||||
if img is not None:
|
||||
self.__image = img
|
||||
else:
|
||||
self.__image = bpy.data.images.load(
|
||||
rel_filepath, check_existing=True
|
||||
)
|
||||
self.__image.name = imgname
|
||||
|
||||
self.__image.gl_load()
|
||||
|
||||
if self.__image and len(self.__image.pixels) == 0:
|
||||
self.__image.reload()
|
||||
self.__image.gl_load()
|
||||
except Exception as e:
|
||||
print(f"BL_UI_BUTTON: exception in set_image(): {e}")
|
||||
self.__image = None
|
||||
|
||||
def set_image_colorspace(self, colorspace):
|
||||
image_utils.set_colorspace(self.__image, colorspace)
|
||||
|
||||
def get_image_path(self):
|
||||
self.check_image_exists()
|
||||
if self.__image is None:
|
||||
return None
|
||||
return self.__image.filepath
|
||||
|
||||
def update(self, x, y):
|
||||
super().update(x, y)
|
||||
|
||||
def draw(self):
|
||||
if not self._is_visible:
|
||||
return
|
||||
|
||||
self.shader.bind()
|
||||
|
||||
self.batch_panel.draw(self.shader)
|
||||
|
||||
self.draw_image()
|
||||
|
||||
def draw_image(self):
|
||||
if self.__image is not None:
|
||||
y_screen_flip = self.get_area_height() - self.y_screen
|
||||
off_x, off_y = self.__image_position
|
||||
sx, sy = self.__image_size
|
||||
ui_bgl.draw_image(
|
||||
self.x_screen + off_x,
|
||||
y_screen_flip - off_y - sy,
|
||||
sx,
|
||||
sy,
|
||||
self.__image,
|
||||
1.0,
|
||||
crop=(0, 0, 1, 1),
|
||||
batch=None,
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def set_mouse_down(self, mouse_down_func):
|
||||
self.mouse_down_func = mouse_down_func
|
||||
|
||||
def mouse_down(self, x, y):
|
||||
return False
|
||||
|
||||
def mouse_move(self, x, y):
|
||||
return
|
||||
|
||||
def mouse_up(self, x, y):
|
||||
return
|
||||
@@ -0,0 +1,91 @@
|
||||
import blf
|
||||
import bpy
|
||||
|
||||
from .bl_ui_widget import BL_UI_Widget
|
||||
|
||||
|
||||
class BL_UI_Label(BL_UI_Widget):
|
||||
def __init__(self, x, y, width, height):
|
||||
super().__init__(x, y, width, height)
|
||||
|
||||
self._text_color = (1.0, 1.0, 1.0, 1.0)
|
||||
self._text = "Label"
|
||||
self._text_size = 16
|
||||
self._halign = "LEFT"
|
||||
self._valign = "TOP"
|
||||
# multiline
|
||||
self.multiline = False
|
||||
self.row_height = 20
|
||||
|
||||
@property
|
||||
def text_color(self):
|
||||
return self._text_color
|
||||
|
||||
@text_color.setter
|
||||
def text_color(self, value):
|
||||
if value != self._text_color:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._text_color = value
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
if value != self._text:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._text = value
|
||||
|
||||
@property
|
||||
def text_size(self):
|
||||
return self._text_size
|
||||
|
||||
@text_size.setter
|
||||
def text_size(self, value):
|
||||
if value != self._text_size:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._text_size = value
|
||||
|
||||
def is_in_rect(self, x, y):
|
||||
return False
|
||||
|
||||
def draw(self):
|
||||
if not self._is_visible:
|
||||
return
|
||||
|
||||
area_height = self.get_area_height()
|
||||
|
||||
font_id = 1
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
blf.size(font_id, self._text_size, 72)
|
||||
else:
|
||||
blf.size(font_id, self._text_size)
|
||||
|
||||
textpos_y = area_height - self.y_screen - self.height
|
||||
|
||||
r, g, b, a = self._text_color
|
||||
x = self.x_screen
|
||||
y = textpos_y
|
||||
if self._halign != "LEFT":
|
||||
width, height = blf.dimensions(font_id, self._text)
|
||||
if self._halign == "RIGHT":
|
||||
x -= width
|
||||
elif self._halign == "CENTER":
|
||||
x -= width // 2
|
||||
if self._valign == "CENTER":
|
||||
y -= height // 2
|
||||
# bottom could be here but there's no reason for it
|
||||
if not self.multiline:
|
||||
blf.position(font_id, x, y, 0)
|
||||
|
||||
blf.color(font_id, r, g, b, a)
|
||||
|
||||
blf.draw(font_id, self._text)
|
||||
else:
|
||||
lines = self._text.split("\n")
|
||||
for line in lines:
|
||||
blf.position(font_id, x, y, 0)
|
||||
blf.color(font_id, r, g, b, a)
|
||||
blf.draw(font_id, line)
|
||||
y -= self.row_height
|
||||
@@ -0,0 +1,235 @@
|
||||
import bpy
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
class BL_UI_Widget:
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.x_screen = x
|
||||
self.y_screen = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._bg_color = (0.8, 0.8, 0.8, 1.0)
|
||||
self._tag = None
|
||||
self.context = None
|
||||
self.__inrect = False
|
||||
self._mouse_down = False
|
||||
self._mouse_down_right = False
|
||||
self._is_visible = True
|
||||
self._is_active = True # if the widget needs to be disabled
|
||||
|
||||
def set_location(self, x, y):
|
||||
# if self.x != x or self.y != y or self.x_screen != x or self.y_screen != y:
|
||||
# bpy.context.region.tag_redraw()
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.x_screen = x
|
||||
self.y_screen = y
|
||||
self.update(x, y)
|
||||
|
||||
@property
|
||||
def bg_color(self):
|
||||
return self._bg_color
|
||||
|
||||
@bg_color.setter
|
||||
def bg_color(self, value):
|
||||
if value != self._bg_color:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._bg_color = value
|
||||
|
||||
@property
|
||||
def visible(self):
|
||||
return self._is_visible
|
||||
|
||||
@visible.setter
|
||||
def visible(self, value):
|
||||
if value != self._is_visible:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._is_visible = value
|
||||
|
||||
@property
|
||||
def active(self):
|
||||
return self._is_active
|
||||
|
||||
@visible.setter
|
||||
def active(self, value):
|
||||
if value != self._is_active:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._is_active = value
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
return self._tag
|
||||
|
||||
@tag.setter
|
||||
def tag(self, value):
|
||||
self._tag = value
|
||||
|
||||
def draw(self):
|
||||
if not self._is_visible:
|
||||
return
|
||||
|
||||
self.shader.bind()
|
||||
self.shader.uniform_float("color", self._bg_color)
|
||||
|
||||
self.batch_panel.draw(self.shader)
|
||||
|
||||
def init(self, context):
|
||||
self.context = context
|
||||
self.update(self.x, self.y)
|
||||
|
||||
def update(self, x, y):
|
||||
area_height = self.get_area_height()
|
||||
self.x_screen = x
|
||||
self.y_screen = y
|
||||
|
||||
indices = ((0, 1, 2), (0, 2, 3))
|
||||
|
||||
y_screen_flip = area_height - self.y_screen
|
||||
|
||||
# bottom left, top left, top right, bottom right
|
||||
vertices = (
|
||||
(self.x_screen, y_screen_flip),
|
||||
(self.x_screen, y_screen_flip - self.height),
|
||||
(self.x_screen + self.width, y_screen_flip - self.height),
|
||||
(self.x_screen + self.width, y_screen_flip),
|
||||
)
|
||||
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
self.shader = gpu.shader.from_builtin("2D_UNIFORM_COLOR")
|
||||
else:
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
self.batch_panel = batch_for_shader(
|
||||
self.shader, "TRIS", {"pos": vertices}, indices=indices
|
||||
)
|
||||
bpy.context.region.tag_redraw()
|
||||
|
||||
def handle_event(self, event):
|
||||
"""
|
||||
returns True if the event was handled by the widget
|
||||
# 'handled_pass', if the event was handled but the event should be passed to other widgets
|
||||
False if the event was not handled by the widget
|
||||
"""
|
||||
|
||||
if not self._is_visible:
|
||||
return False
|
||||
if not self._is_active:
|
||||
return False
|
||||
|
||||
x = event.mouse_region_x
|
||||
y = event.mouse_region_y
|
||||
|
||||
if event.type == "LEFTMOUSE":
|
||||
if event.value == "PRESS":
|
||||
self._mouse_down = True
|
||||
bpy.context.region.tag_redraw()
|
||||
return self.mouse_down(x, y)
|
||||
else:
|
||||
self._mouse_down = False
|
||||
bpy.context.region.tag_redraw()
|
||||
self.mouse_up(x, y)
|
||||
return False
|
||||
|
||||
elif event.type == "RIGHTMOUSE":
|
||||
if event.value == "PRESS":
|
||||
self._mouse_down_right = True
|
||||
bpy.context.region.tag_redraw()
|
||||
return self.mouse_down_right(x, y)
|
||||
else:
|
||||
self._mouse_down_right = False
|
||||
bpy.context.region.tag_redraw()
|
||||
self.mouse_up(x, y)
|
||||
|
||||
elif event.type == "MOUSEMOVE":
|
||||
self.mouse_move(x, y)
|
||||
inrect = self.is_in_rect(x, y)
|
||||
|
||||
# we enter the rect
|
||||
if not self.__inrect and inrect:
|
||||
self.__inrect = True
|
||||
self.mouse_enter(event, x, y)
|
||||
# we tag redraw since the hover colors are picked in the draw function
|
||||
bpy.context.region.tag_redraw()
|
||||
|
||||
# we are leaving the rect
|
||||
elif self.__inrect and not inrect:
|
||||
self.__inrect = False
|
||||
self.mouse_exit(event, x, y)
|
||||
bpy.context.region.tag_redraw()
|
||||
|
||||
# return always false to enable mouse exit events on other buttons.(would sometimes not hide the tooltip)
|
||||
return False # self.__inrect
|
||||
|
||||
elif (
|
||||
event.value == "PRESS"
|
||||
and self.__inrect
|
||||
and (event.ascii != "" or event.type in self.get_input_keys())
|
||||
):
|
||||
return self.text_input(event)
|
||||
|
||||
return False
|
||||
|
||||
def get_input_keys(self):
|
||||
return []
|
||||
|
||||
def get_area_height(self):
|
||||
return self.context.area.height
|
||||
|
||||
def is_in_rect(self, x, y):
|
||||
area_height = self.get_area_height()
|
||||
|
||||
widget_y = area_height - self.y_screen
|
||||
if (self.x_screen <= x <= (self.x_screen + self.width)) and (
|
||||
widget_y >= y >= (widget_y - self.height)
|
||||
):
|
||||
# print('is in rect!?')
|
||||
# print('area height', area_height)
|
||||
# print ('x sceen ',self.x_screen,'x ', x, 'width', self.width)
|
||||
# print ('widghet y', widget_y,'y', y, 'height',self.height)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def text_input(self, event):
|
||||
return False
|
||||
|
||||
def mouse_down(self, x, y):
|
||||
return self.is_in_rect(x, y)
|
||||
|
||||
def mouse_down_right(self, x, y):
|
||||
return self.is_in_rect(x, y)
|
||||
|
||||
def mouse_up(self, x, y):
|
||||
pass
|
||||
|
||||
def mouse_enter_func(self, widget):
|
||||
pass
|
||||
|
||||
def mouse_exit_func(self, widget):
|
||||
pass
|
||||
|
||||
def set_mouse_enter(self, mouse_enter_func):
|
||||
self.mouse_enter_func = mouse_enter_func
|
||||
|
||||
def call_mouse_enter(self):
|
||||
if self.mouse_enter_func:
|
||||
self.mouse_enter_func(self)
|
||||
|
||||
def mouse_enter(self, event, x, y):
|
||||
self.call_mouse_enter()
|
||||
|
||||
def set_mouse_exit(self, mouse_exit_func):
|
||||
self.mouse_exit_func = mouse_exit_func
|
||||
|
||||
def call_mouse_exit(self):
|
||||
if self.mouse_exit_func:
|
||||
self.mouse_exit_func(self)
|
||||
|
||||
def mouse_exit(self, event, x, y):
|
||||
self.call_mouse_exit()
|
||||
|
||||
def mouse_move(self, x, y):
|
||||
pass
|
||||
Reference in New Issue
Block a user