Merge branch 'blender-v4.0-release'
[blender-addons.git] / object_collection_manager / qcd_move_widget.py
blob818b72968bf096e4f48866cdf406650e3b1b0940
1 # SPDX-FileCopyrightText: 2011 Ryan Inch
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import time
6 from math import cos, sin, pi, floor
7 import bpy
8 import blf
9 import gpu
10 from gpu_extras.batch import batch_for_shader
12 from bpy.types import Operator
14 from . import internals
16 from .qcd_operators import (
17 get_move_selection,
18 get_move_active,
21 def spacer():
22 spacer = 10
23 return round(spacer * scale_factor())
25 def scale_factor():
26 return bpy.context.preferences.system.ui_scale
28 def get_coords(area):
29 x = area["vert"][0]
30 y = area["vert"][1]
31 w = area["width"]
32 h = area["height"]
34 vertices = (
35 (x, y-h), # bottom left
36 (x+w, y-h), # bottom right
37 (x, y), # top left
38 (x+w, y)) # top right
40 indices = (
41 (0, 1, 2), (2, 1, 3))
43 return vertices, indices
45 def get_x_coords(area):
46 x = area["vert"][0]
47 y = area["vert"][1]
48 w = area["width"]
49 h = area["height"]
51 vertices = (
52 (x, y), # top left A
53 (x+(w*0.1), y), # top left B
54 (x+w, y), # top right A
55 (x+w-(w*0.1), y), # top right B
56 (x, y-h), # bottom left A
57 (x+(w*0.1), y-h), # bottom left B
58 (x+w, y-h), # bottom right A
59 (x+w-(w*0.1), y-h), # bottom right B
60 (x+(w/2)-(w*0.05), y-(h/2)), # center left
61 (x+(w/2)+(w*0.05), y-(h/2)) # center right
64 indices = (
65 (0,1,8), (1,8,9), # top left bar
66 (2,3,9), (3,9,8), # top right bar
67 (4,5,8), (5,8,9), # bottom left bar
68 (6,7,8), (6,9,8) # bottom right bar
71 return vertices, indices
73 def get_circle_coords(area):
74 # set x, y to center
75 x = area["vert"][0] + area["width"] / 2
76 y = area["vert"][1] - area["width"] / 2
77 radius = area["width"] / 2
78 sides = 32
79 vertices = [(radius * cos(side * 2 * pi / sides) + x,
80 radius * sin(side * 2 * pi / sides) + y)
81 for side in range(sides + 1)]
83 return vertices
85 def draw_rounded_rect(area, shader, color, tl=5, tr=5, bl=5, br=5, outline=False):
86 sides = 32
88 tl = round(tl * scale_factor())
89 tr = round(tr * scale_factor())
90 bl = round(bl * scale_factor())
91 br = round(br * scale_factor())
93 gpu.state.blend_set('ALPHA')
95 if outline:
96 thickness = round(2 * scale_factor())
97 thickness = max(thickness, 2)
98 shader.uniform_float("lineWidth", thickness)
100 draw_type = 'TRI_FAN' if not outline else 'LINE_STRIP'
102 # top left corner
103 vert_x = area["vert"][0] + tl
104 vert_y = area["vert"][1] - tl
105 tl_vert = (vert_x, vert_y)
106 vertices = [(vert_x, vert_y)] if not outline else []
108 for side in range(sides+1):
109 if (8<=side<=16):
110 cosine = tl * cos(side * 2 * pi / sides) + vert_x
111 sine = tl * sin(side * 2 * pi / sides) + vert_y
112 vertices.append((cosine,sine))
114 if not outline:
115 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
116 shader.bind()
117 shader.uniform_float("color", color)
118 batch.draw(shader)
119 else:
120 batch = batch_for_shader(shader, draw_type, {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
121 shader.bind()
122 batch.draw(shader)
124 # top right corner
125 vert_x = area["vert"][0] + area["width"] - tr
126 vert_y = area["vert"][1] - tr
127 tr_vert = (vert_x, vert_y)
128 vertices = [(vert_x, vert_y)] if not outline else []
130 for side in range(sides+1):
131 if (0<=side<=8):
132 cosine = tr * cos(side * 2 * pi / sides) + vert_x
133 sine = tr * sin(side * 2 * pi / sides) + vert_y
134 vertices.append((cosine,sine))
136 if not outline:
137 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
138 shader.bind()
139 shader.uniform_float("color", color)
140 batch.draw(shader)
141 else:
142 batch = batch_for_shader(shader, draw_type, {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
143 shader.bind()
144 batch.draw(shader)
146 # bottom left corner
147 vert_x = area["vert"][0] + bl
148 vert_y = area["vert"][1] - area["height"] + bl
149 bl_vert = (vert_x, vert_y)
150 vertices = [(vert_x, vert_y)] if not outline else []
152 for side in range(sides+1):
153 if (16<=side<=24):
154 cosine = bl * cos(side * 2 * pi / sides) + vert_x
155 sine = bl * sin(side * 2 * pi / sides) + vert_y
156 vertices.append((cosine,sine))
158 if not outline:
159 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
160 shader.bind()
161 shader.uniform_float("color", color)
162 batch.draw(shader)
163 else:
164 batch = batch_for_shader(shader, draw_type, {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
165 shader.bind()
166 batch.draw(shader)
168 # bottom right corner
169 vert_x = area["vert"][0] + area["width"] - br
170 vert_y = area["vert"][1] - area["height"] + br
171 br_vert = (vert_x, vert_y)
172 vertices = [(vert_x, vert_y)] if not outline else []
174 for side in range(sides+1):
175 if (24<=side<=32):
176 cosine = br * cos(side * 2 * pi / sides) + vert_x
177 sine = br * sin(side * 2 * pi / sides) + vert_y
178 vertices.append((cosine,sine))
180 if not outline:
181 batch = batch_for_shader(shader, draw_type, {"pos": vertices})
182 shader.bind()
183 shader.uniform_float("color", color)
184 batch.draw(shader)
185 else:
186 batch = batch_for_shader(shader, draw_type, {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
187 shader.bind()
188 batch.draw(shader)
190 if not outline:
191 vertices = []
192 indices = []
193 base_ind = 0
195 # left edge
196 width = max(tl, bl)
197 le_x = tl_vert[0]-tl
198 vertices.extend([
199 (le_x, tl_vert[1]),
200 (le_x+width, tl_vert[1]),
201 (le_x, bl_vert[1]),
202 (le_x+width, bl_vert[1])
204 indices.extend([
205 (base_ind,base_ind+1,base_ind+2),
206 (base_ind+2,base_ind+3,base_ind+1)
208 base_ind += 4
210 # right edge
211 width = max(tr, br)
212 re_x = tr_vert[0]+tr
213 vertices.extend([
214 (re_x, tr_vert[1]),
215 (re_x-width, tr_vert[1]),
216 (re_x, br_vert[1]),
217 (re_x-width, br_vert[1])
219 indices.extend([
220 (base_ind,base_ind+1,base_ind+2),
221 (base_ind+2,base_ind+3,base_ind+1)
223 base_ind += 4
225 # top edge
226 width = max(tl, tr)
227 te_y = tl_vert[1]+tl
228 vertices.extend([
229 (tl_vert[0], te_y),
230 (tl_vert[0], te_y-width),
231 (tr_vert[0], te_y),
232 (tr_vert[0], te_y-width)
234 indices.extend([
235 (base_ind,base_ind+1,base_ind+2),
236 (base_ind+2,base_ind+3,base_ind+1)
238 base_ind += 4
240 # bottom edge
241 width = max(bl, br)
242 be_y = bl_vert[1]-bl
243 vertices.extend([
244 (bl_vert[0], be_y),
245 (bl_vert[0], be_y+width),
246 (br_vert[0], be_y),
247 (br_vert[0], be_y+width)
249 indices.extend([
250 (base_ind,base_ind+1,base_ind+2),
251 (base_ind+2,base_ind+3,base_ind+1)
253 base_ind += 4
255 # middle
256 vertices.extend([
257 tl_vert,
258 tr_vert,
259 bl_vert,
260 br_vert
262 indices.extend([
263 (base_ind,base_ind+1,base_ind+2),
264 (base_ind+2,base_ind+3,base_ind+1)
267 batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
268 shader.bind()
270 shader.uniform_float("color", color)
271 batch.draw(shader)
273 else:
274 overlap = round(thickness / 2 - scale_factor() / 2)
276 # left edge
277 le_x = tl_vert[0]-tl
278 vertices = [
279 (le_x, tl_vert[1] + (overlap if tl == 0 else 0)),
280 (le_x, bl_vert[1] - (overlap if bl == 0 else 0))
283 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
284 shader.bind()
285 batch.draw(shader)
287 # right edge
288 re_x = tr_vert[0]+tr
289 vertices = [
290 (re_x, tr_vert[1] + (overlap if tr == 0 else 0)),
291 (re_x, br_vert[1] - (overlap if br == 0 else 0))
294 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
295 shader.bind()
296 batch.draw(shader)
298 # top edge
299 te_y = tl_vert[1]+tl
300 vertices = [
301 (tl_vert[0] - (overlap if tl == 0 else 0), te_y),
302 (tr_vert[0] + (overlap if tr == 0 else 0), te_y)
305 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
306 shader.bind()
307 batch.draw(shader)
309 # bottom edge
310 be_y = bl_vert[1]-bl
311 vertices = [
312 (bl_vert[0] - (overlap if bl == 0 else 0), be_y),
313 (br_vert[0] + (overlap if br == 0 else 0), be_y)
316 batch = batch_for_shader(shader, 'LINE_STRIP', {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
317 shader.bind()
318 batch.draw(shader)
321 gpu.state.blend_set('NONE')
323 def mouse_in_area(mouse_pos, area, buf = 0):
324 x = mouse_pos[0]
325 y = mouse_pos[1]
327 # check left
328 if x+buf < area["vert"][0]:
329 return False
331 # check right
332 if x-buf > area["vert"][0] + area["width"]:
333 return False
335 # check top
336 if y-buf > area["vert"][1]:
337 return False
339 # check bottom
340 if y+buf < area["vert"][1] - area["height"]:
341 return False
343 # if we reach here we're in the area
344 return True
346 def account_for_view_bounds(area):
347 # make sure it renders in the 3d view - prioritize top left
349 # right
350 if area["vert"][0] + area["width"] > bpy.context.region.width:
351 x = bpy.context.region.width - area["width"]
352 y = area["vert"][1]
354 area["vert"] = (x, y)
356 # left
357 if area["vert"][0] < 0:
358 x = 0
359 y = area["vert"][1]
361 area["vert"] = (x, y)
363 # bottom
364 if area["vert"][1] - area["height"] < 0:
365 x = area["vert"][0]
366 y = area["height"]
368 area["vert"] = (x, y)
370 # top
371 if area["vert"][1] > bpy.context.region.height:
372 x = area["vert"][0]
373 y = bpy.context.region.height
375 area["vert"] = (x, y)
377 def update_area_dimensions(area, w=0, h=0):
378 area["width"] += w
379 area["height"] += h
381 class QCDMoveWidget(Operator):
382 """Move objects to QCD Slots"""
383 bl_idname = "view3d.qcd_move_widget"
384 bl_label = "QCD Move Widget"
386 slots = {
387 "ONE":1,
388 "TWO":2,
389 "THREE":3,
390 "FOUR":4,
391 "FIVE":5,
392 "SIX":6,
393 "SEVEN":7,
394 "EIGHT":8,
395 "NINE":9,
396 "ZERO":10,
399 last_type = ''
400 last_type_value = ''
401 initialized = False
402 moved = False
404 def modal(self, context, event):
405 if event.type == 'TIMER':
406 if self.hover_time and self.hover_time + 0.5 < time.time():
407 self.draw_tooltip = True
409 context.area.tag_redraw()
410 return {'RUNNING_MODAL'}
413 context.area.tag_redraw()
415 if len(self.areas) == 1:
416 return {'RUNNING_MODAL'}
418 if self.last_type == 'LEFTMOUSE' and self.last_type_value == 'PRESS' and event.type == 'MOUSEMOVE':
419 if mouse_in_area(self.mouse_pos, self.areas["Grab Bar"]):
420 x_offset = self.areas["Main Window"]["vert"][0] - self.mouse_pos[0]
421 x = event.mouse_region_x + x_offset
423 y_offset = self.areas["Main Window"]["vert"][1] - self.mouse_pos[1]
424 y = event.mouse_region_y + y_offset
426 self.areas["Main Window"]["vert"] = (x, y)
428 self.mouse_pos = (event.mouse_region_x, event.mouse_region_y)
430 elif event.type == 'MOUSEMOVE':
431 self.draw_tooltip = False
432 self.hover_time = None
433 self.mouse_pos = (event.mouse_region_x, event.mouse_region_y)
435 if not mouse_in_area(self.mouse_pos, self.areas["Main Window"], 50 * scale_factor()):
436 if self.initialized:
437 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
439 if self.moved:
440 bpy.ops.ed.undo_push()
442 return {'FINISHED'}
444 else:
445 self.initialized = True
447 elif event.value == 'PRESS' and event.type == 'LEFTMOUSE':
448 if not mouse_in_area(self.mouse_pos, self.areas["Main Window"], 10 * scale_factor()):
449 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
451 if self.moved:
452 bpy.ops.ed.undo_push()
454 return {'FINISHED'}
456 for num in range(20):
457 if not self.areas.get(f"Button {num + 1}", None):
458 continue
460 if mouse_in_area(self.mouse_pos, self.areas[f"Button {num + 1}"]):
461 bpy.ops.view3d.move_to_qcd_slot(slot=str(num + 1), toggle=event.shift)
462 self.moved = True
464 elif event.type in {'RIGHTMOUSE', 'ESC'}:
465 bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
467 return {'CANCELLED'}
469 if event.value == 'PRESS' and event.type in self.slots:
470 move_to = self.slots[event.type]
472 if event.alt:
473 move_to += 10
475 if event.shift:
476 bpy.ops.view3d.move_to_qcd_slot(slot=str(move_to), toggle=True)
477 else:
478 bpy.ops.view3d.move_to_qcd_slot(slot=str(move_to), toggle=False)
480 self.moved = True
482 if event.type != 'MOUSEMOVE' and event.type != 'INBETWEEN_MOUSEMOVE':
483 self.last_type = event.type
484 self.last_type_value = event.value
486 return {'RUNNING_MODAL'}
488 def invoke(self, context, event):
489 if context.area.type == 'VIEW_3D':
490 # the arguments we pass the the callback
491 args = (self, context)
492 # Add the region OpenGL drawing callback
493 # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
494 self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
495 self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
497 self.mouse_pos = (event.mouse_region_x, event.mouse_region_y)
499 self.draw_tooltip = False
501 self.hover_time = None
503 self.areas = {}
505 # MAIN WINDOW BACKGROUND
506 x = self.mouse_pos[0] - spacer()*2
507 y = self.mouse_pos[1] + spacer()*2
508 main_window = {
509 # Top Left Vertex
510 "vert": (x,y),
511 "width": 0,
512 "height": 0,
513 "value": None
516 self.areas["Main Window"] = main_window
517 allocate_main_ui(self, context)
518 account_for_view_bounds(main_window)
520 context.window_manager.modal_handler_add(self)
521 return {'RUNNING_MODAL'}
523 else:
524 self.report({'WARNING'}, "View3D not found, cannot run operator")
525 return {'CANCELLED'}
528 def allocate_main_ui(self, context):
529 main_window = self.areas["Main Window"]
530 self.areas.clear()
531 main_window["width"] = 0
532 main_window["height"] = 0
533 self.areas["Main Window"] = main_window
535 cur_width_pos = main_window["vert"][0]
536 cur_height_pos = main_window["vert"][1]
538 # GRAB BAR
539 grab_bar = {
540 "vert": main_window["vert"],
541 "width": 0,
542 "height": round(23 * scale_factor()),
543 "value": None
546 # add grab bar to areas
547 self.areas["Grab Bar"] = grab_bar
550 # WINDOW TITLE
551 wt_indent_x = spacer()*2
552 wt_y_offset = round(spacer()/2)
553 window_title = {
554 "vert": main_window["vert"],
555 "width": 0,
556 "height": round(13 * scale_factor()),
557 "value": "Move Objects to QCD Slots"
560 x = main_window["vert"][0] + wt_indent_x
561 y = main_window["vert"][1] - window_title["height"] - wt_y_offset
562 window_title["vert"] = (x, y)
564 # add window title to areas
565 self.areas["Window Title"] = window_title
567 cur_height_pos = window_title["vert"][1]
570 # MAIN BUTTON AREA
571 button_size = round(20 * scale_factor())
572 button_gap = round(1 * scale_factor())
573 button_group = 5
574 button_group_gap = round(20 * scale_factor())
575 button_group_width = button_size * button_group + button_gap * (button_group - 1)
577 mba_indent_x = spacer()*2
578 mba_outdent_x = spacer()*2
579 mba_indent_y = spacer()
580 x = cur_width_pos + mba_indent_x
581 y = cur_height_pos - mba_indent_y
582 main_button_area = {
583 "vert": (x, y),
584 "width": 0,
585 "height": 0,
586 "value": None
589 # add main button area to areas
590 self.areas["Main Button Area"] = main_button_area
592 # update current position
593 cur_width_pos = main_button_area["vert"][0]
594 cur_height_pos = main_button_area["vert"][1]
597 # BUTTON ROW 1 A
598 button_row_1_a = {
599 "vert": main_button_area["vert"],
600 "width": button_group_width,
601 "height": button_size,
602 "value": None
605 # add button row 1 A to areas
606 self.areas["Button Row 1 A"] = button_row_1_a
608 # advance width pos to start of next row
609 cur_width_pos += button_row_1_a["width"]
610 cur_width_pos += button_group_gap
612 # BUTTON ROW 1 B
613 x = cur_width_pos
614 y = cur_height_pos
615 button_row_1_b = {
616 "vert": (x, y),
617 "width": button_group_width,
618 "height": button_size,
619 "value": None
622 # add button row 1 B to areas
623 self.areas["Button Row 1 B"] = button_row_1_b
625 # reset width pos to start of main button area
626 cur_width_pos = main_button_area["vert"][0]
627 # update height pos
628 cur_height_pos -= button_row_1_a["height"]
629 # add gap between button rows
630 cur_height_pos -= button_gap
633 # BUTTON ROW 2 A
634 x = cur_width_pos
635 y = cur_height_pos
636 button_row_2_a = {
637 "vert": (x, y),
638 "width": button_group_width,
639 "height": button_size,
640 "value": None
643 # add button row 2 A to areas
644 self.areas["Button Row 2 A"] = button_row_2_a
646 # advance width pos to start of next row
647 cur_width_pos += button_row_2_a["width"]
648 cur_width_pos += button_group_gap
650 # BUTTON ROW 2 B
651 x = cur_width_pos
652 y = cur_height_pos
653 button_row_2_b = {
654 "vert": (x, y),
655 "width": button_group_width,
656 "height": button_size,
657 "value": None
660 # add button row 2 B to areas
661 self.areas["Button Row 2 B"] = button_row_2_b
664 selected_objects = get_move_selection()
665 active_object = get_move_active()
668 # BUTTONS
669 def get_buttons(button_row, row_num):
670 cur_width_pos = button_row["vert"][0]
671 cur_height_pos = button_row["vert"][1]
672 for num in range(button_group):
673 slot_num = row_num + num
675 qcd_slot_name = internals.qcd_slots.get_name(f"{slot_num}")
677 if qcd_slot_name:
678 qcd_laycol = internals.layer_collections[qcd_slot_name]["ptr"]
679 collection_objects = qcd_laycol.collection.objects
681 # BUTTON
682 x = cur_width_pos
683 y = cur_height_pos
684 button = {
685 "vert": (x, y),
686 "width": button_size,
687 "height": button_size,
688 "value": slot_num
691 self.areas[f"Button {slot_num}"] = button
693 # ACTIVE OBJECT ICON
694 if active_object and active_object in selected_objects and active_object.name in collection_objects:
695 x = cur_width_pos + round(button_size / 4)
696 y = cur_height_pos - round(button_size / 4)
697 active_object_indicator = {
698 "vert": (x, y),
699 "width": floor(button_size / 2),
700 "height": floor(button_size / 2),
701 "value": None
704 self.areas[f"Button {slot_num} Active Object Indicator"] = active_object_indicator
706 elif not set(selected_objects).isdisjoint(collection_objects):
707 x = cur_width_pos + round(button_size / 4) + floor(1 * scale_factor())
708 y = cur_height_pos - round(button_size / 4) - floor(1 * scale_factor())
709 selected_object_indicator = {
710 "vert": (x, y),
711 "width": floor(button_size / 2) - floor(1 * scale_factor()),
712 "height": floor(button_size / 2) - floor(1 * scale_factor()),
713 "value": None
716 self.areas[f"Button {slot_num} Selected Object Indicator"] = selected_object_indicator
718 elif collection_objects:
719 x = cur_width_pos + floor(button_size / 4)
720 y = cur_height_pos - button_size / 2 + 1 * scale_factor()
721 object_indicator = {
722 "vert": (x, y),
723 "width": round(button_size / 2),
724 "height": round(2 * scale_factor()),
725 "value": None
727 self.areas[f"Button {slot_num} Object Indicator"] = object_indicator
729 else:
730 x = cur_width_pos + 2 * scale_factor()
731 y = cur_height_pos - 2 * scale_factor()
732 X_icon = {
733 "vert": (x, y),
734 "width": button_size - 4 * scale_factor(),
735 "height": button_size - 4 * scale_factor(),
736 "value": None
739 self.areas[f"X_icon {slot_num}"] = X_icon
741 cur_width_pos += button_size
742 cur_width_pos += button_gap
744 get_buttons(button_row_1_a, 1)
745 get_buttons(button_row_1_b, 6)
746 get_buttons(button_row_2_a, 11)
747 get_buttons(button_row_2_b, 16)
750 # UPDATE DYNAMIC DIMENSIONS
751 width = button_row_1_a["width"] + button_group_gap + button_row_1_b["width"]
752 height = button_row_1_a["height"] + button_gap + button_row_2_a["height"]
753 update_area_dimensions(main_button_area, width, height)
755 width = main_button_area["width"] + mba_indent_x + mba_outdent_x
756 height = main_button_area["height"] + mba_indent_y * 2 + window_title["height"] + wt_y_offset
757 update_area_dimensions(main_window, width, height)
759 update_area_dimensions(grab_bar, main_window["width"])
762 def draw_callback_px(self, context):
763 allocate_main_ui(self, context)
765 shader = gpu.shader.from_builtin('UNIFORM_COLOR')
766 line_shader = gpu.shader.from_builtin('POLYLINE_SMOOTH_COLOR')
768 addon_prefs = context.preferences.addons[__package__].preferences
770 # main window background
771 main_window = self.areas["Main Window"]
772 outline_color = addon_prefs.qcd_ogl_widget_menu_back_outline
773 background_color = addon_prefs.qcd_ogl_widget_menu_back_inner
774 draw_rounded_rect(main_window, line_shader, outline_color[:] + (1,), outline=True)
775 draw_rounded_rect(main_window, shader, background_color)
777 # draw window title
778 window_title = self.areas["Window Title"]
779 x = window_title["vert"][0]
780 y = window_title["vert"][1]
781 h = window_title["height"]
782 text = window_title["value"]
783 text_color = addon_prefs.qcd_ogl_widget_menu_back_text
784 font_id = 0
785 blf.position(font_id, x, y, 0)
786 blf.size(font_id, int(h))
787 blf.color(font_id, text_color[0], text_color[1], text_color[2], 1)
788 blf.draw(font_id, text)
790 in_tooltip_area = False
791 tooltip_slot_idx = None
793 for num in range(20):
794 slot_num = num + 1
795 qcd_slot_name = internals.qcd_slots.get_name(f"{slot_num}")
796 if qcd_slot_name:
797 qcd_laycol = internals.layer_collections[qcd_slot_name]["ptr"]
798 collection_objects = qcd_laycol.collection.objects
799 selected_objects = get_move_selection()
800 active_object = get_move_active()
801 button_area = self.areas[f"Button {slot_num}"]
803 # colors
804 button_color = addon_prefs.qcd_ogl_widget_tool_inner
805 icon_color = addon_prefs.qcd_ogl_widget_tool_text
806 if not qcd_laycol.exclude:
807 button_color = addon_prefs.qcd_ogl_widget_tool_inner_sel
808 icon_color = addon_prefs.qcd_ogl_widget_tool_text_sel
810 if mouse_in_area(self.mouse_pos, button_area):
811 in_tooltip_area = True
812 tooltip_slot_idx = slot_num
814 mod = 0.1
816 if button_color[0] + mod > 1 or button_color[1] + mod > 1 or button_color[2] + mod > 1:
817 mod = -mod
819 button_color = (
820 button_color[0] + mod,
821 button_color[1] + mod,
822 button_color[2] + mod,
823 button_color[3]
827 # button roundness
828 tl = tr = bl = br = 0
829 rounding = 5
831 if num < 10:
832 if not internals.qcd_slots.contains(idx=f"{num+2}"):
833 tr = rounding
835 if not internals.qcd_slots.contains(idx=f"{num}"):
836 tl = rounding
837 else:
838 if not internals.qcd_slots.contains(idx=f"{num+2}"):
839 br = rounding
841 if not internals.qcd_slots.contains(idx=f"{num}"):
842 bl = rounding
844 if num in [0,5]:
845 tl = rounding
846 elif num in [4,9]:
847 tr = rounding
848 elif num in [10,15]:
849 bl = rounding
850 elif num in [14,19]:
851 br = rounding
853 # draw button
854 outline_color = addon_prefs.qcd_ogl_widget_tool_outline
855 draw_rounded_rect(button_area, line_shader, outline_color[:] + (1,), tl, tr, bl, br, outline=True)
856 draw_rounded_rect(button_area, shader, button_color, tl, tr, bl, br)
858 # ACTIVE OBJECT
859 if active_object and active_object in selected_objects and active_object.name in collection_objects:
860 active_object_indicator = self.areas[f"Button {slot_num} Active Object Indicator"]
862 vertices = get_circle_coords(active_object_indicator)
863 batch = batch_for_shader(shader, 'TRI_FAN', {"pos": vertices})
864 shader.bind()
865 shader.uniform_float("color", icon_color[:] + (1,))
867 gpu.state.blend_set('ALPHA')
869 batch.draw(shader)
871 gpu.state.blend_set('NONE')
873 # SELECTED OBJECTS
874 elif not set(selected_objects).isdisjoint(collection_objects):
875 selected_object_indicator = self.areas[f"Button {slot_num} Selected Object Indicator"]
877 alpha = addon_prefs.qcd_ogl_selected_icon_alpha
878 vertices = get_circle_coords(selected_object_indicator)
879 line_shader.uniform_float("lineWidth", 2 * scale_factor())
880 color = icon_color[:] + (alpha,)
881 batch = batch_for_shader(line_shader, 'LINE_STRIP', {"pos": [(v[0], v[1], 0) for v in vertices], "color": [color for v in vertices]})
882 shader.bind()
884 gpu.state.blend_set('ALPHA')
886 batch.draw(line_shader)
888 gpu.state.blend_set('NONE')
890 # OBJECTS
891 elif collection_objects:
892 object_indicator = self.areas[f"Button {slot_num} Object Indicator"]
894 alpha = addon_prefs.qcd_ogl_objects_icon_alpha
895 vertices, indices = get_coords(object_indicator)
896 batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
897 shader.bind()
898 shader.uniform_float("color", icon_color[:] + (alpha,))
900 gpu.state.blend_set('ALPHA')
902 batch.draw(shader)
904 gpu.state.blend_set('NONE')
907 # X ICON
908 else:
909 X_icon = self.areas[f"X_icon {slot_num}"]
910 X_icon_color = addon_prefs.qcd_ogl_widget_menu_back_text
912 vertices, indices = get_x_coords(X_icon)
913 batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)
914 shader.bind()
915 shader.uniform_float("color", X_icon_color[:] + (1,))
917 gpu.state.blend_set('ALPHA')
919 batch.draw(shader)
921 gpu.state.blend_set('NONE')
923 if in_tooltip_area:
924 if self.draw_tooltip:
925 slot_name = internals.qcd_slots.get_name(f"{tooltip_slot_idx}")
926 slot_string = f"QCD Slot {tooltip_slot_idx}: \"{slot_name}\"\n"
927 hotkey_string = (
928 " * LMB - Move objects to slot.\n"
929 " * Shift+LMB - Toggle objects\' slot."
932 draw_tooltip(self, context, shader, line_shader, f"{slot_string}{hotkey_string}")
934 self.hover_time = None
936 else:
937 if not self.hover_time:
938 self.hover_time = time.time()
941 def draw_tooltip(self, context, shader, line_shader, message):
942 addon_prefs = context.preferences.addons[__package__].preferences
944 font_id = 0
945 line_height = 11 * scale_factor()
946 text_color = addon_prefs.qcd_ogl_widget_tooltip_text
947 blf.size(font_id, int(line_height))
948 blf.color(font_id, text_color[0], text_color[1], text_color[2], 1)
950 lines = message.split("\n")
951 longest = [0,""]
952 num_lines = len(lines)
954 for line in lines:
955 w, _ = blf.dimensions(font_id, line)
957 if w > longest[0]:
958 longest[0] = w
959 longest[1] = line
961 w, h = blf.dimensions(font_id, longest[1])
963 line_spacer = 1 * scale_factor()
964 padding = 4 * scale_factor()
966 # draw background
967 tooltip = {
968 "vert": self.mouse_pos,
969 "width": w + spacer()*2,
970 "height": (line_height * num_lines + line_spacer * num_lines) + padding*3,
971 "value": None
974 x = tooltip["vert"][0] - spacer()*2
975 y = tooltip["vert"][1] + tooltip["height"] + round(5 * scale_factor())
976 tooltip["vert"] = (x, y)
978 account_for_view_bounds(tooltip)
980 outline_color = addon_prefs.qcd_ogl_widget_tooltip_outline
981 background_color = addon_prefs.qcd_ogl_widget_tooltip_inner
982 draw_rounded_rect(tooltip, line_shader, outline_color[:] + (1,), outline=True)
983 draw_rounded_rect(tooltip, shader, background_color)
985 line_pos = padding + line_height
986 # draw text
987 for num, line in enumerate(lines):
988 x = tooltip["vert"][0] + spacer()
989 y = tooltip["vert"][1] - line_pos
990 blf.position(font_id, x, y, 0)
991 blf.draw(font_id, line)
993 line_pos += line_height + line_spacer