Fix T78854: Cell fracture fails in background mode
[blender-addons.git] / object_collection_manager / qcd_move_widget.py
blob55da145319568bf6efba0883398f9687c24946cb
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Copyright 2011, Ryan Inch
5 import time
6 from math import cos, sin, pi, floor
7 import bpy
8 import bgl
9 import blf
10 import gpu
11 from gpu_extras.batch import batch_for_shader
13 from bpy.types import Operator
15 from . import internals
17 from .qcd_operators import (
18 get_move_selection,
19 get_move_active,
22 def spacer():
23 spacer = 10
24 return round(spacer * scale_factor())
26 def scale_factor():
27 return bpy.context.preferences.system.ui_scale
29 def get_coords(area):
30 x = area["vert"][0]
31 y = area["vert"][1]
32 w = area["width"]
33 h = area["height"]
35 vertices = (
36 (x, y-h), # bottom left
37 (x+w, y-h), # bottom right
38 (x, y), # top left
39 (x+w, y)) # top right
41 indices = (
42 (0, 1, 2), (2, 1, 3))
44 return vertices, indices
46 def get_x_coords(area):
47 x = area["vert"][0]
48 y = area["vert"][1]
49 w = area["width"]
50 h = area["height"]
52 vertices = (
53 (x, y), # top left A
54 (x+(w*0.1), y), # top left B
55 (x+w, y), # top right A
56 (x+w-(w*0.1), y), # top right B
57 (x, y-h), # bottom left A
58 (x+(w*0.1), y-h), # bottom left B
59 (x+w, y-h), # bottom right A
60 (x+w-(w*0.1), y-h), # bottom right B
61 (x+(w/2)-(w*0.05), y-(h/2)), # center left
62 (x+(w/2)+(w*0.05), y-(h/2)) # center right
65 indices = (
66 (0,1,8), (1,8,9), # top left bar
67 (2,3,9), (3,9,8), # top right bar
68 (4,5,8), (5,8,9), # bottom left bar
69 (6,7,8), (6,9,8) # bottom right bar
72 return vertices, indices
74 def get_circle_coords(area):
75 # set x, y to center
76 x = area["vert"][0] + area["width"] / 2
77 y = area["vert"][1] - area["width"] / 2
78 radius = area["width"] / 2
79 sides = 32
80 vertices = [(radius * cos(side * 2 * pi / sides) + x,
81 radius * sin(side * 2 * pi / sides) + y)
82 for side in range(sides + 1)]
84 return vertices
86 def draw_rounded_rect(area, shader, color, tl=5, tr=5, bl=5, br=5, outline=False):
87 sides = 32
89 tl = round(tl * scale_factor())
90 tr = round(tr * scale_factor())
91 bl = round(bl * scale_factor())
92 br = round(br * scale_factor())
94 bgl.glEnable(bgl.GL_BLEND)
96 if outline:
97 thickness = round(2 * scale_factor())
98 thickness = max(thickness, 2)
100 bgl.glLineWidth(thickness)
101 bgl.glEnable(bgl.GL_LINE_SMOOTH)
102 bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
104 draw_type = 'TRI_FAN' if not outline else 'LINE_STRIP'
106 # top left corner
107 vert_x = area["vert"][0] + tl
108 vert_y = area["vert"][1] - tl
109 tl_vert = (vert_x, vert_y)
110 vertices = [(vert_x, vert_y)] if not outline else []
112 for side in range(sides+1):
113 if (8<=side<=16):
114 cosine = tl * cos(side * 2 * pi / sides) + vert_x
115 sine = tl * sin(side * 2 * pi / sides) + vert_y
116 vertices.append((cosine,sine))
118 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
119 shader.bind()
120 shader.uniform_float("color", color)
121 batch.draw(shader)
123 # top right corner
124 vert_x = area["vert"][0] + area["width"] - tr
125 vert_y = area["vert"][1] - tr
126 tr_vert = (vert_x, vert_y)
127 vertices = [(vert_x, vert_y)] if not outline else []
129 for side in range(sides+1):
130 if (0<=side<=8):
131 cosine = tr * cos(side * 2 * pi / sides) + vert_x
132 sine = tr * sin(side * 2 * pi / sides) + vert_y
133 vertices.append((cosine,sine))
135 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
136 shader.bind()
137 shader.uniform_float("color", color)
138 batch.draw(shader)
140 # bottom left corner
141 vert_x = area["vert"][0] + bl
142 vert_y = area["vert"][1] - area["height"] + bl
143 bl_vert = (vert_x, vert_y)
144 vertices = [(vert_x, vert_y)] if not outline else []
146 for side in range(sides+1):
147 if (16<=side<=24):
148 cosine = bl * cos(side * 2 * pi / sides) + vert_x
149 sine = bl * sin(side * 2 * pi / sides) + vert_y
150 vertices.append((cosine,sine))
152 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
153 shader.bind()
154 shader.uniform_float("color", color)
155 batch.draw(shader)
157 # bottom right corner
158 vert_x = area["vert"][0] + area["width"] - br
159 vert_y = area["vert"][1] - area["height"] + br
160 br_vert = (vert_x, vert_y)
161 vertices = [(vert_x, vert_y)] if not outline else []
163 for side in range(sides+1):
164 if (24<=side<=32):
165 cosine = br * cos(side * 2 * pi / sides) + vert_x
166 sine = br * sin(side * 2 * pi / sides) + vert_y
167 vertices.append((cosine,sine))
169 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
170 shader.bind()
171 shader.uniform_float("color", color)
172 batch.draw(shader)
174 if not outline:
175 vertices = []
176 indices = []
177 base_ind = 0
179 # left edge
180 width = max(tl, bl)
181 le_x = tl_vert[0]-tl
182 vertices.extend([
183 (le_x, tl_vert[1]),
184 (le_x+width, tl_vert[1]),
185 (le_x, bl_vert[1]),
186 (le_x+width, bl_vert[1])
188 indices.extend([
189 (base_ind,base_ind+1,base_ind+2),
190 (base_ind+2,base_ind+3,base_ind+1)
192 base_ind += 4
194 # right edge
195 width = max(tr, br)
196 re_x = tr_vert[0]+tr
197 vertices.extend([
198 (re_x, tr_vert[1]),
199 (re_x-width, tr_vert[1]),
200 (re_x, br_vert[1]),
201 (re_x-width, br_vert[1])
203 indices.extend([
204 (base_ind,base_ind+1,base_ind+2),
205 (base_ind+2,base_ind+3,base_ind+1)
207 base_ind += 4
209 # top edge
210 width = max(tl, tr)
211 te_y = tl_vert[1]+tl
212 vertices.extend([
213 (tl_vert[0], te_y),
214 (tl_vert[0], te_y-width),
215 (tr_vert[0], te_y),
216 (tr_vert[0], te_y-width)
218 indices.extend([
219 (base_ind,base_ind+1,base_ind+2),
220 (base_ind+2,base_ind+3,base_ind+1)
222 base_ind += 4
224 # bottom edge
225 width = max(bl, br)
226 be_y = bl_vert[1]-bl
227 vertices.extend([
228 (bl_vert[0], be_y),
229 (bl_vert[0], be_y+width),
230 (br_vert[0], be_y),
231 (br_vert[0], be_y+width)
233 indices.extend([
234 (base_ind,base_ind+1,base_ind+2),
235 (base_ind+2,base_ind+3,base_ind+1)
237 base_ind += 4
239 # middle
240 vertices.extend([
241 tl_vert,
242 tr_vert,
243 bl_vert,
244 br_vert
246 indices.extend([
247 (base_ind,base_ind+1,base_ind+2),
248 (base_ind+2,base_ind+3,base_ind+1)
251 batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
253 shader.uniform_float("color", color)
254 batch.draw(shader)
256 else:
257 overlap = round(thickness / 2 - scale_factor() / 2)
259 # left edge
260 le_x = tl_vert[0]-tl
261 vertices = [
262 (le_x, tl_vert[1] + (overlap if tl == 0 else 0)),
263 (le_x, bl_vert[1] - (overlap if bl == 0 else 0))
266 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices})
267 batch.draw(shader)
269 # right edge
270 re_x = tr_vert[0]+tr
271 vertices = [
272 (re_x, tr_vert[1] + (overlap if tr == 0 else 0)),
273 (re_x, br_vert[1] - (overlap if br == 0 else 0))
276 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices})
277 batch.draw(shader)
279 # top edge
280 te_y = tl_vert[1]+tl
281 vertices = [
282 (tl_vert[0] - (overlap if tl == 0 else 0), te_y),
283 (tr_vert[0] + (overlap if tr == 0 else 0), te_y)
286 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices})
287 batch.draw(shader)
289 # bottom edge
290 be_y = bl_vert[1]-bl
291 vertices = [
292 (bl_vert[0] - (overlap if bl == 0 else 0), be_y),
293 (br_vert[0] + (overlap if br == 0 else 0), be_y)
296 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices})
297 batch.draw(shader)
299 bgl.glDisable(bgl.GL_LINE_SMOOTH)
301 bgl.glDisable(bgl.GL_BLEND)
303 def mouse_in_area(mouse_pos, area, buf = 0):
304 x = mouse_pos[0]
305 y = mouse_pos[1]
307 # check left
308 if x+buf < area["vert"][0]:
309 return False
311 # check right
312 if x-buf > area["vert"][0] + area["width"]:
313 return False
315 # check top
316 if y-buf > area["vert"][1]:
317 return False
319 # check bottom
320 if y+buf < area["vert"][1] - area["height"]:
321 return False
323 # if we reach here we're in the area
324 return True
326 def account_for_view_bounds(area):
327 # make sure it renders in the 3d view - prioritize top left
329 # right
330 if area["vert"][0] + area["width"] > bpy.context.region.width:
331 x = bpy.context.region.width - area["width"]
332 y = area["vert"][1]
334 area["vert"] = (x, y)
336 # left
337 if area["vert"][0] < 0:
338 x = 0
339 y = area["vert"][1]
341 area["vert"] = (x, y)
343 # bottom
344 if area["vert"][1] - area["height"] < 0:
345 x = area["vert"][0]
346 y = area["height"]
348 area["vert"] = (x, y)
350 # top
351 if area["vert"][1] > bpy.context.region.height:
352 x = area["vert"][0]
353 y = bpy.context.region.height
355 area["vert"] = (x, y)
357 def update_area_dimensions(area, w=0, h=0):
358 area["width"] += w
359 area["height"] += h
361 class QCDMoveWidget(Operator):
362 """Move objects to QCD Slots"""
363 bl_idname = "view3d.qcd_move_widget"
364 bl_label = "QCD Move Widget"
366 slots = {
367 "ONE":1,
368 "TWO":2,
369 "THREE":3,
370 "FOUR":4,
371 "FIVE":5,
372 "SIX":6,
373 "SEVEN":7,
374 "EIGHT":8,
375 "NINE":9,
376 "ZERO":10,
379 last_type = ''
380 initialized = False
381 moved = False
383 def modal(self, context, event):
384 if event.type == 'TIMER':
385 if self.hover_time and self.hover_time + 0.5 < time.time():
386 self.draw_tooltip = True
388 context.area.tag_redraw()
389 return {'RUNNING_MODAL'}
392 context.area.tag_redraw()
394 if len(self.areas) == 1:
395 return {'RUNNING_MODAL'}
397 if self.last_type == 'LEFTMOUSE' and event.value == 'PRESS' and event.type == 'MOUSEMOVE':
398 if mouse_in_area(self.mouse_pos, self.areas["Grab Bar"]):
399 x_offset = self.areas["Main Window"]["vert"][0] - self.mouse_pos[0]
400 x = event.mouse_region_x + x_offset
402 y_offset = self.areas["Main Window"]["vert"][1] - self.mouse_pos[1]
403 y = event.mouse_region_y + y_offset
405 self.areas["Main Window"]["vert"] = (x, y)
407 self.mouse_pos = (event.mouse_region_x, event.mouse_region_y)
409 elif event.type == 'MOUSEMOVE':
410 self.draw_tooltip = False
411 self.hover_time = None
412 self.mouse_pos = (event.mouse_region_x, event.mouse_region_y)
414 if not mouse_in_area(self.mouse_pos, self.areas["Main Window"], 50 * scale_factor()):
415 if self.initialized:
416 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
418 if self.moved:
419 bpy.ops.ed.undo_push()
421 return {'FINISHED'}
423 else:
424 self.initialized = True
426 elif event.value == 'PRESS' and event.type == 'LEFTMOUSE':
427 if not mouse_in_area(self.mouse_pos, self.areas["Main Window"], 10 * scale_factor()):
428 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
430 if self.moved:
431 bpy.ops.ed.undo_push()
433 return {'FINISHED'}
435 for num in range(20):
436 if not self.areas.get(f"Button {num + 1}", None):
437 continue
439 if mouse_in_area(self.mouse_pos, self.areas[f"Button {num + 1}"]):
440 bpy.ops.view3d.move_to_qcd_slot(slot=str(num + 1), toggle=event.shift)
441 self.moved = True
443 elif event.type in {'RIGHTMOUSE', 'ESC'}:
444 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
446 return {'CANCELLED'}
448 if event.value == 'PRESS' and event.type in self.slots:
449 move_to = self.slots[event.type]
451 if event.alt:
452 move_to += 10
454 if event.shift:
455 bpy.ops.view3d.move_to_qcd_slot(slot=str(move_to), toggle=True)
456 else:
457 bpy.ops.view3d.move_to_qcd_slot(slot=str(move_to), toggle=False)
459 self.moved = True
461 if event.type != 'MOUSEMOVE' and event.type != 'INBETWEEN_MOUSEMOVE':
462 self.last_type = event.type
464 return {'RUNNING_MODAL'}
466 def invoke(self, context, event):
467 if context.area.type == 'VIEW_3D':
468 # the arguments we pass the the callback
469 args = (self, context)
470 # Add the region OpenGL drawing callback
471 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
472 self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
473 self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
475 self.mouse_pos = (event.mouse_region_x, event.mouse_region_y)
477 self.draw_tooltip = False
479 self.hover_time = None
481 self.areas = {}
483 # MAIN WINDOW BACKGROUND
484 x = self.mouse_pos[0] - spacer()*2
485 y = self.mouse_pos[1] + spacer()*2
486 main_window = {
487 # Top Left Vertex
488 "vert": (x,y),
489 "width": 0,
490 "height": 0,
491 "value": None
494 self.areas["Main Window"] = main_window
495 allocate_main_ui(self, context)
496 account_for_view_bounds(main_window)
498 context.window_manager.modal_handler_add(self)
499 return {'RUNNING_MODAL'}
501 else:
502 self.report({'WARNING'}, "View3D not found, cannot run operator")
503 return {'CANCELLED'}
506 def allocate_main_ui(self, context):
507 main_window = self.areas["Main Window"]
508 self.areas.clear()
509 main_window["width"] = 0
510 main_window["height"] = 0
511 self.areas["Main Window"] = main_window
513 cur_width_pos = main_window["vert"][0]
514 cur_height_pos = main_window["vert"][1]
516 # GRAB BAR
517 grab_bar = {
518 "vert": main_window["vert"],
519 "width": 0,
520 "height": round(23 * scale_factor()),
521 "value": None
524 # add grab bar to areas
525 self.areas["Grab Bar"] = grab_bar
528 # WINDOW TITLE
529 wt_indent_x = spacer()*2
530 wt_y_offset = round(spacer()/2)
531 window_title = {
532 "vert": main_window["vert"],
533 "width": 0,
534 "height": round(13 * scale_factor()),
535 "value": "Move Objects to QCD Slots"
538 x = main_window["vert"][0] + wt_indent_x
539 y = main_window["vert"][1] - window_title["height"] - wt_y_offset
540 window_title["vert"] = (x, y)
542 # add window title to areas
543 self.areas["Window Title"] = window_title
545 cur_height_pos = window_title["vert"][1]
548 # MAIN BUTTON AREA
549 button_size = round(20 * scale_factor())
550 button_gap = round(1 * scale_factor())
551 button_group = 5
552 button_group_gap = round(20 * scale_factor())
553 button_group_width = button_size * button_group + button_gap * (button_group - 1)
555 mba_indent_x = spacer()*2
556 mba_outdent_x = spacer()*2
557 mba_indent_y = spacer()
558 x = cur_width_pos + mba_indent_x
559 y = cur_height_pos - mba_indent_y
560 main_button_area = {
561 "vert": (x, y),
562 "width": 0,
563 "height": 0,
564 "value": None
567 # add main button area to areas
568 self.areas["Main Button Area"] = main_button_area
570 # update current position
571 cur_width_pos = main_button_area["vert"][0]
572 cur_height_pos = main_button_area["vert"][1]
575 # BUTTON ROW 1 A
576 button_row_1_a = {
577 "vert": main_button_area["vert"],
578 "width": button_group_width,
579 "height": button_size,
580 "value": None
583 # add button row 1 A to areas
584 self.areas["Button Row 1 A"] = button_row_1_a
586 # advance width pos to start of next row
587 cur_width_pos += button_row_1_a["width"]
588 cur_width_pos += button_group_gap
590 # BUTTON ROW 1 B
591 x = cur_width_pos
592 y = cur_height_pos
593 button_row_1_b = {
594 "vert": (x, y),
595 "width": button_group_width,
596 "height": button_size,
597 "value": None
600 # add button row 1 B to areas
601 self.areas["Button Row 1 B"] = button_row_1_b
603 # reset width pos to start of main button area
604 cur_width_pos = main_button_area["vert"][0]
605 # update height pos
606 cur_height_pos -= button_row_1_a["height"]
607 # add gap between button rows
608 cur_height_pos -= button_gap
611 # BUTTON ROW 2 A
612 x = cur_width_pos
613 y = cur_height_pos
614 button_row_2_a = {
615 "vert": (x, y),
616 "width": button_group_width,
617 "height": button_size,
618 "value": None
621 # add button row 2 A to areas
622 self.areas["Button Row 2 A"] = button_row_2_a
624 # advance width pos to start of next row
625 cur_width_pos += button_row_2_a["width"]
626 cur_width_pos += button_group_gap
628 # BUTTON ROW 2 B
629 x = cur_width_pos
630 y = cur_height_pos
631 button_row_2_b = {
632 "vert": (x, y),
633 "width": button_group_width,
634 "height": button_size,
635 "value": None
638 # add button row 2 B to areas
639 self.areas["Button Row 2 B"] = button_row_2_b
642 selected_objects = get_move_selection()
643 active_object = get_move_active()
646 # BUTTONS
647 def get_buttons(button_row, row_num):
648 cur_width_pos = button_row["vert"][0]
649 cur_height_pos = button_row["vert"][1]
650 for num in range(button_group):
651 slot_num = row_num + num
653 qcd_slot_name = internals.qcd_slots.get_name(f"{slot_num}")
655 if qcd_slot_name:
656 qcd_laycol = internals.layer_collections[qcd_slot_name]["ptr"]
657 collection_objects = qcd_laycol.collection.objects
659 # BUTTON
660 x = cur_width_pos
661 y = cur_height_pos
662 button = {
663 "vert": (x, y),
664 "width": button_size,
665 "height": button_size,
666 "value": slot_num
669 self.areas[f"Button {slot_num}"] = button
671 # ACTIVE OBJECT ICON
672 if active_object and active_object in selected_objects and active_object.name in collection_objects:
673 x = cur_width_pos + round(button_size / 4)
674 y = cur_height_pos - round(button_size / 4)
675 active_object_indicator = {
676 "vert": (x, y),
677 "width": floor(button_size / 2),
678 "height": floor(button_size / 2),
679 "value": None
682 self.areas[f"Button {slot_num} Active Object Indicator"] = active_object_indicator
684 elif not set(selected_objects).isdisjoint(collection_objects):
685 x = cur_width_pos + round(button_size / 4) + floor(1 * scale_factor())
686 y = cur_height_pos - round(button_size / 4) - floor(1 * scale_factor())
687 selected_object_indicator = {
688 "vert": (x, y),
689 "width": floor(button_size / 2) - floor(1 * scale_factor()),
690 "height": floor(button_size / 2) - floor(1 * scale_factor()),
691 "value": None
694 self.areas[f"Button {slot_num} Selected Object Indicator"] = selected_object_indicator
696 elif collection_objects:
697 x = cur_width_pos + floor(button_size / 4)
698 y = cur_height_pos - button_size / 2 + 1 * scale_factor()
699 object_indicator = {
700 "vert": (x, y),
701 "width": round(button_size / 2),
702 "height": round(2 * scale_factor()),
703 "value": None
705 self.areas[f"Button {slot_num} Object Indicator"] = object_indicator
707 else:
708 x = cur_width_pos + 2 * scale_factor()
709 y = cur_height_pos - 2 * scale_factor()
710 X_icon = {
711 "vert": (x, y),
712 "width": button_size - 4 * scale_factor(),
713 "height": button_size - 4 * scale_factor(),
714 "value": None
717 self.areas[f"X_icon {slot_num}"] = X_icon
719 cur_width_pos += button_size
720 cur_width_pos += button_gap
722 get_buttons(button_row_1_a, 1)
723 get_buttons(button_row_1_b, 6)
724 get_buttons(button_row_2_a, 11)
725 get_buttons(button_row_2_b, 16)
728 # UPDATE DYNAMIC DIMENSIONS
729 width = button_row_1_a["width"] + button_group_gap + button_row_1_b["width"]
730 height = button_row_1_a["height"] + button_gap + button_row_2_a["height"]
731 update_area_dimensions(main_button_area, width, height)
733 width = main_button_area["width"] + mba_indent_x + mba_outdent_x
734 height = main_button_area["height"] + mba_indent_y * 2 + window_title["height"] + wt_y_offset
735 update_area_dimensions(main_window, width, height)
737 update_area_dimensions(grab_bar, main_window["width"])
740 def draw_callback_px(self, context):
741 allocate_main_ui(self, context)
743 shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
744 shader.bind()
746 addon_prefs = context.preferences.addons[__package__].preferences
748 # main window background
749 main_window = self.areas["Main Window"]
750 outline_color = addon_prefs.qcd_ogl_widget_menu_back_outline
751 background_color = addon_prefs.qcd_ogl_widget_menu_back_inner
752 draw_rounded_rect(main_window, shader, outline_color[:] + (1,), outline=True)
753 draw_rounded_rect(main_window, shader, background_color)
755 # draw window title
756 window_title = self.areas["Window Title"]
757 x = window_title["vert"][0]
758 y = window_title["vert"][1]
759 h = window_title["height"]
760 text = window_title["value"]
761 text_color = addon_prefs.qcd_ogl_widget_menu_back_text
762 font_id = 0
763 blf.position(font_id, x, y, 0)
764 blf.size(font_id, int(h), 72)
765 blf.color(font_id, text_color[0], text_color[1], text_color[2], 1)
766 blf.draw(font_id, text)
768 # refresh shader - not sure why this is needed
769 shader.bind()
771 in_tooltip_area = False
772 tooltip_slot_idx = None
774 for num in range(20):
775 slot_num = num + 1
776 qcd_slot_name = internals.qcd_slots.get_name(f"{slot_num}")
777 if qcd_slot_name:
778 qcd_laycol = internals.layer_collections[qcd_slot_name]["ptr"]
779 collection_objects = qcd_laycol.collection.objects
780 selected_objects = get_move_selection()
781 active_object = get_move_active()
782 button_area = self.areas[f"Button {slot_num}"]
784 # colors
785 button_color = addon_prefs.qcd_ogl_widget_tool_inner
786 icon_color = addon_prefs.qcd_ogl_widget_tool_text
787 if not qcd_laycol.exclude:
788 button_color = addon_prefs.qcd_ogl_widget_tool_inner_sel
789 icon_color = addon_prefs.qcd_ogl_widget_tool_text_sel
791 if mouse_in_area(self.mouse_pos, button_area):
792 in_tooltip_area = True
793 tooltip_slot_idx = slot_num
795 mod = 0.1
797 if button_color[0] + mod > 1 or button_color[1] + mod > 1 or button_color[2] + mod > 1:
798 mod = -mod
800 button_color = (
801 button_color[0] + mod,
802 button_color[1] + mod,
803 button_color[2] + mod,
804 button_color[3]
808 # button roundness
809 tl = tr = bl = br = 0
810 rounding = 5
812 if num < 10:
813 if not internals.qcd_slots.contains(idx=f"{num+2}"):
814 tr = rounding
816 if not internals.qcd_slots.contains(idx=f"{num}"):
817 tl = rounding
818 else:
819 if not internals.qcd_slots.contains(idx=f"{num+2}"):
820 br = rounding
822 if not internals.qcd_slots.contains(idx=f"{num}"):
823 bl = rounding
825 if num in [0,5]:
826 tl = rounding
827 elif num in [4,9]:
828 tr = rounding
829 elif num in [10,15]:
830 bl = rounding
831 elif num in [14,19]:
832 br = rounding
834 # draw button
835 outline_color = addon_prefs.qcd_ogl_widget_tool_outline
836 draw_rounded_rect(button_area, shader, outline_color[:] + (1,), tl, tr, bl, br, outline=True)
837 draw_rounded_rect(button_area, shader, button_color, tl, tr, bl, br)
839 # ACTIVE OBJECT
840 if active_object and active_object in selected_objects and active_object.name in collection_objects:
841 active_object_indicator = self.areas[f"Button {slot_num} Active Object Indicator"]
843 vertices = get_circle_coords(active_object_indicator)
844 shader.uniform_float("color", icon_color[:] + (1,))
845 batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
847 bgl.glEnable(bgl.GL_BLEND)
849 batch.draw(shader)
851 bgl.glDisable(bgl.GL_BLEND)
853 # SELECTED OBJECTS
854 elif not set(selected_objects).isdisjoint(collection_objects):
855 selected_object_indicator = self.areas[f"Button {slot_num} Selected Object Indicator"]
857 alpha = addon_prefs.qcd_ogl_selected_icon_alpha
858 vertices = get_circle_coords(selected_object_indicator)
859 shader.uniform_float("color", icon_color[:] + (alpha,))
860 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": vertices})
862 bgl.glLineWidth(2 * scale_factor())
863 bgl.glEnable(bgl.GL_BLEND)
864 bgl.glEnable(bgl.GL_LINE_SMOOTH)
865 bgl.glHint(bgl.GL_LINE_SMOOTH_HINT, bgl.GL_NICEST)
867 batch.draw(shader)
869 bgl.glDisable(bgl.GL_LINE_SMOOTH)
870 bgl.glDisable(bgl.GL_BLEND)
872 # OBJECTS
873 elif collection_objects:
874 object_indicator = self.areas[f"Button {slot_num} Object Indicator"]
876 alpha = addon_prefs.qcd_ogl_objects_icon_alpha
877 vertices, indices = get_coords(object_indicator)
878 shader.uniform_float("color", icon_color[:] + (alpha,))
879 batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
881 bgl.glEnable(bgl.GL_BLEND)
883 batch.draw(shader)
885 bgl.glDisable(bgl.GL_BLEND)
888 # X ICON
889 else:
890 X_icon = self.areas[f"X_icon {slot_num}"]
891 X_icon_color = addon_prefs.qcd_ogl_widget_menu_back_text
893 vertices, indices = get_x_coords(X_icon)
894 shader.uniform_float("color", X_icon_color[:] + (1,))
895 batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
897 bgl.glEnable(bgl.GL_BLEND)
898 bgl.glEnable(bgl.GL_POLYGON_SMOOTH)
899 bgl.glHint(bgl.GL_POLYGON_SMOOTH_HINT, bgl.GL_NICEST)
901 batch.draw(shader)
903 bgl.glDisable(bgl.GL_POLYGON_SMOOTH)
904 bgl.glDisable(bgl.GL_BLEND)
906 if in_tooltip_area:
907 if self.draw_tooltip:
908 slot_name = internals.qcd_slots.get_name(f"{tooltip_slot_idx}")
909 slot_string = f"QCD Slot {tooltip_slot_idx}: \"{slot_name}\"\n"
910 hotkey_string = (
911 " * LMB - Move objects to slot.\n"
912 " * Shift+LMB - Toggle objects\' slot."
915 draw_tooltip(self, context, shader, f"{slot_string}{hotkey_string}")
917 self.hover_time = None
919 else:
920 if not self.hover_time:
921 self.hover_time = time.time()
924 def draw_tooltip(self, context, shader, message):
925 addon_prefs = context.preferences.addons[__package__].preferences
927 font_id = 0
928 line_height = 11 * scale_factor()
929 text_color = addon_prefs.qcd_ogl_widget_tooltip_text
930 blf.size(font_id, int(line_height), 72)
931 blf.color(font_id, text_color[0], text_color[1], text_color[2], 1)
933 lines = message.split("\n")
934 longest = [0,""]
935 num_lines = len(lines)
937 for line in lines:
938 w, _ = blf.dimensions(font_id, line)
940 if w > longest[0]:
941 longest[0] = w
942 longest[1] = line
944 w, h = blf.dimensions(font_id, longest[1])
946 line_spacer = 1 * scale_factor()
947 padding = 4 * scale_factor()
949 # draw background
950 tooltip = {
951 "vert": self.mouse_pos,
952 "width": w + spacer()*2,
953 "height": (line_height * num_lines + line_spacer * num_lines) + padding*3,
954 "value": None
957 x = tooltip["vert"][0] - spacer()*2
958 y = tooltip["vert"][1] + tooltip["height"] + round(5 * scale_factor())
959 tooltip["vert"] = (x, y)
961 account_for_view_bounds(tooltip)
963 outline_color = addon_prefs.qcd_ogl_widget_tooltip_outline
964 background_color = addon_prefs.qcd_ogl_widget_tooltip_inner
965 draw_rounded_rect(tooltip, shader, outline_color[:] + (1,), outline=True)
966 draw_rounded_rect(tooltip, shader, background_color)
968 line_pos = padding + line_height
969 # draw text
970 for num, line in enumerate(lines):
971 x = tooltip["vert"][0] + spacer()
972 y = tooltip["vert"][1] - line_pos
973 blf.position(font_id, x, y, 0)
974 blf.draw(font_id, line)
976 line_pos += line_height + line_spacer