Node Wrangler: do not rely on image name to detect Viewer Node image
[blender-addons.git] / development_icon_get.py
blobe1ac1936d775717b52342f5b0fd71b28d8d16436
1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 bl_info = {
7 "name": "Icon Viewer",
8 "description": "Click an icon to copy its name to the clipboard",
9 "author": "roaoao",
10 "version": (1, 4, 1),
11 "blender": (3, 4, 0),
12 "location": "Text Editor > Dev Tab > Icon Viewer",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/development/icon_viewer.html",
14 "category": "Development",
17 import bpy
18 import math
19 from bpy.props import (
20 BoolProperty,
21 StringProperty,
24 DPI = 72
25 POPUP_PADDING = 10
26 PANEL_PADDING = 44
27 WIN_PADDING = 32
28 ICON_SIZE = 20
29 HISTORY_SIZE = 100
30 HISTORY = []
33 def ui_scale():
34 prefs = bpy.context.preferences.system
35 return prefs.dpi / DPI
38 def prefs():
39 return bpy.context.preferences.addons[__name__].preferences
42 class Icons:
43 def __init__(self, is_popup=False):
44 self._filtered_icons = None
45 self._filter = ""
46 self.filter = ""
47 self.selected_icon = ""
48 self.is_popup = is_popup
50 @property
51 def filter(self):
52 return self._filter
54 @filter.setter
55 def filter(self, value):
56 if self._filter == value:
57 return
59 self._filter = value
60 self.update()
62 @property
63 def filtered_icons(self):
64 if self._filtered_icons is None:
65 self._filtered_icons = []
66 icon_filter = self._filter.upper()
67 self.filtered_icons.clear()
68 pr = prefs()
70 icons = bpy.types.UILayout.bl_rna.functions[
71 "prop"].parameters["icon"].enum_items.keys()
72 for icon in icons:
73 if icon == 'NONE' or \
74 icon_filter and icon_filter not in icon or \
75 not pr.show_brush_icons and "BRUSH_" in icon and \
76 icon != 'BRUSH_DATA' or \
77 not pr.show_matcap_icons and "MATCAP_" in icon or \
78 not pr.show_event_icons and (
79 "EVENT_" in icon or "MOUSE_" in icon
80 ) or \
81 not pr.show_colorset_icons and "COLORSET_" in icon:
82 continue
83 self._filtered_icons.append(icon)
85 return self._filtered_icons
87 @property
88 def num_icons(self):
89 return len(self.filtered_icons)
91 def update(self):
92 if self._filtered_icons is not None:
93 self._filtered_icons.clear()
94 self._filtered_icons = None
96 def draw(self, layout, num_cols=0, icons=None):
97 if icons:
98 filtered_icons = reversed(icons)
99 else:
100 filtered_icons = self.filtered_icons
102 column = layout.column(align=True)
103 row = column.row(align=True)
104 row.alignment = 'CENTER'
106 selected_icon = self.selected_icon if self.is_popup else \
107 bpy.context.window_manager.clipboard
108 col_idx = 0
109 for i, icon in enumerate(filtered_icons):
110 p = row.operator(
111 IV_OT_icon_select.bl_idname, text="",
112 icon=icon, emboss=icon == selected_icon)
113 p.icon = icon
114 p.force_copy_on_select = not self.is_popup
116 col_idx += 1
117 if col_idx > num_cols - 1:
118 if icons:
119 break
120 col_idx = 0
121 if i < len(filtered_icons) - 1:
122 row = column.row(align=True)
123 row.alignment = 'CENTER'
125 if col_idx != 0 and not icons and i >= num_cols:
126 for _ in range(num_cols - col_idx):
127 row.label(text="", icon='BLANK1')
129 if not filtered_icons:
130 row.label(text="No icons were found")
133 class IV_Preferences(bpy.types.AddonPreferences):
134 bl_idname = __name__
136 panel_icons = Icons()
137 popup_icons = Icons(is_popup=True)
139 def update_icons(self, context):
140 self.panel_icons.update()
141 self.popup_icons.update()
143 def set_panel_filter(self, value):
144 self.panel_icons.filter = value
146 panel_filter: StringProperty(
147 description="Filter",
148 default="",
149 get=lambda s: s.panel_icons.filter,
150 set=set_panel_filter,
151 options={'TEXTEDIT_UPDATE'})
152 show_panel_icons: BoolProperty(
153 name="Show Icons",
154 description="Show icons", default=True)
155 show_history: BoolProperty(
156 name="Show History",
157 description="Show history", default=True)
158 show_brush_icons: BoolProperty(
159 name="Show Brush Icons",
160 description="Show brush icons", default=True,
161 update=update_icons)
162 show_matcap_icons: BoolProperty(
163 name="Show Matcap Icons",
164 description="Show matcap icons", default=True,
165 update=update_icons)
166 show_event_icons: BoolProperty(
167 name="Show Event Icons",
168 description="Show event icons", default=True,
169 update=update_icons)
170 show_colorset_icons: BoolProperty(
171 name="Show Colorset Icons",
172 description="Show colorset icons", default=True,
173 update=update_icons)
174 copy_on_select: BoolProperty(
175 name="Copy Icon On Click",
176 description="Copy icon on click", default=True)
177 close_on_select: BoolProperty(
178 name="Close Popup On Click",
179 description=(
180 "Close the popup on click.\n"
181 "Not supported by some windows (User Preferences, Render)"
183 default=False)
184 auto_focus_filter: BoolProperty(
185 name="Auto Focus Input Field",
186 description="Auto focus input field", default=True)
187 show_panel: BoolProperty(
188 name="Show Panel",
189 description="Show the panel in the Text Editor", default=True)
190 show_header: BoolProperty(
191 name="Show Header",
192 description="Show the header in the Python Console",
193 default=True)
195 def draw(self, context):
196 layout = self.layout
197 row = layout.row()
198 row.scale_y = 1.5
199 row.operator(IV_OT_icons_show.bl_idname)
201 row = layout.row()
203 col = row.column(align=True)
204 col.label(text="Icons:")
205 col.prop(self, "show_matcap_icons")
206 col.prop(self, "show_brush_icons")
207 col.prop(self, "show_colorset_icons")
208 col.prop(self, "show_event_icons")
209 col.separator()
210 col.prop(self, "show_history")
212 col = row.column(align=True)
213 col.label(text="Popup:")
214 col.prop(self, "auto_focus_filter")
215 col.prop(self, "copy_on_select")
216 if self.copy_on_select:
217 col.prop(self, "close_on_select")
219 col = row.column(align=True)
220 col.label(text="Panel:")
221 col.prop(self, "show_panel")
222 if self.show_panel:
223 col.prop(self, "show_panel_icons")
225 col.separator()
226 col.label(text="Header:")
227 col.prop(self, "show_header")
230 class IV_PT_icons(bpy.types.Panel):
231 bl_space_type = "TEXT_EDITOR"
232 bl_region_type = "UI"
233 bl_label = "Icon Viewer"
234 bl_category = "Dev"
235 bl_options = {'DEFAULT_CLOSED'}
237 @staticmethod
238 def tag_redraw():
239 wm = bpy.context.window_manager
240 if not wm:
241 return
243 for w in wm.windows:
244 for a in w.screen.areas:
245 if a.type == 'TEXT_EDITOR':
246 for r in a.regions:
247 if r.type == 'UI':
248 r.tag_redraw()
250 def draw(self, context):
251 pr = prefs()
252 row = self.layout.row(align=True)
253 if pr.show_panel_icons:
254 row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
255 else:
256 row.operator(IV_OT_icons_show.bl_idname)
257 row.operator(
258 IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
260 _, y0 = context.region.view2d.region_to_view(0, 0)
261 _, y1 = context.region.view2d.region_to_view(0, 10)
262 region_scale = 10 / abs(y0 - y1)
264 num_cols = max(
266 (context.region.width - PANEL_PADDING) //
267 math.ceil(ui_scale() * region_scale * ICON_SIZE))
269 col = None
270 if HISTORY and pr.show_history:
271 col = self.layout.column(align=True)
272 pr.panel_icons.draw(col.box(), num_cols, HISTORY)
274 if pr.show_panel_icons:
275 col = col or self.layout.column(align=True)
276 pr.panel_icons.draw(col.box(), num_cols)
278 @classmethod
279 def poll(cls, context):
280 return prefs().show_panel
283 class IV_OT_panel_menu_call(bpy.types.Operator):
284 bl_idname = "iv.panel_menu_call"
285 bl_label = ""
286 bl_description = "Menu"
287 bl_options = {'INTERNAL'}
289 def menu(self, menu, context):
290 pr = prefs()
291 layout = menu.layout
292 layout.prop(pr, "show_panel_icons")
293 layout.prop(pr, "show_history")
295 if not pr.show_panel_icons:
296 return
298 layout.separator()
299 layout.prop(pr, "show_matcap_icons")
300 layout.prop(pr, "show_brush_icons")
301 layout.prop(pr, "show_colorset_icons")
302 layout.prop(pr, "show_event_icons")
304 def execute(self, context):
305 context.window_manager.popup_menu(self.menu, title="Icon Viewer")
306 return {'FINISHED'}
309 class IV_OT_icon_select(bpy.types.Operator):
310 bl_idname = "iv.icon_select"
311 bl_label = ""
312 bl_description = "Select the icon"
313 bl_options = {'INTERNAL'}
315 icon: StringProperty()
316 force_copy_on_select: BoolProperty()
318 def execute(self, context):
319 pr = prefs()
320 pr.popup_icons.selected_icon = self.icon
321 if pr.copy_on_select or self.force_copy_on_select:
322 context.window_manager.clipboard = self.icon
323 self.report({'INFO'}, self.icon)
325 if pr.close_on_select and IV_OT_icons_show.instance:
326 IV_OT_icons_show.instance.close()
328 if pr.show_history:
329 if self.icon in HISTORY:
330 HISTORY.remove(self.icon)
331 if len(HISTORY) >= HISTORY_SIZE:
332 HISTORY.pop(0)
333 HISTORY.append(self.icon)
334 return {'FINISHED'}
337 class IV_OT_icons_show(bpy.types.Operator):
338 bl_idname = "iv.icons_show"
339 bl_label = "Icon Viewer"
340 bl_description = "Icon viewer"
341 bl_property = "filter_auto_focus"
343 instance = None
345 def set_filter(self, value):
346 prefs().popup_icons.filter = value
348 def set_selected_icon(self, value):
349 if IV_OT_icons_show.instance:
350 IV_OT_icons_show.instance.auto_focusable = False
352 filter_auto_focus: StringProperty(
353 description="Filter",
354 get=lambda s: prefs().popup_icons.filter,
355 set=set_filter,
356 options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
357 filter: StringProperty(
358 description="Filter",
359 get=lambda s: prefs().popup_icons.filter,
360 set=set_filter,
361 options={'TEXTEDIT_UPDATE'})
362 selected_icon: StringProperty(
363 description="Selected Icon",
364 get=lambda s: prefs().popup_icons.selected_icon,
365 set=set_selected_icon)
367 def get_num_cols(self, num_icons):
368 return round(1.3 * math.sqrt(num_icons))
370 def draw_header(self, layout):
371 pr = prefs()
372 header = layout.box()
373 header = header.split(factor=0.75) if self.selected_icon else \
374 header.row()
375 row = header.row(align=True)
376 row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
377 row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
378 row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
379 row.prop(pr, "show_event_icons", text="", icon='HAND')
380 row.separator()
382 row.prop(
383 pr, "copy_on_select", text="",
384 icon='COPYDOWN', toggle=True)
385 if pr.copy_on_select:
386 sub = row.row(align=True)
387 if bpy.context.window.screen.name == "temp":
388 sub.alert = True
389 sub.prop(
390 pr, "close_on_select", text="",
391 icon='RESTRICT_SELECT_OFF', toggle=True)
392 row.prop(
393 pr, "auto_focus_filter", text="",
394 icon='OUTLINER_DATA_FONT', toggle=True)
395 row.separator()
397 if self.auto_focusable and pr.auto_focus_filter:
398 row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
399 else:
400 row.prop(self, "filter", text="", icon='VIEWZOOM')
402 if self.selected_icon:
403 row = header.row()
404 row.prop(self, "selected_icon", text="", icon=self.selected_icon)
406 def draw(self, context):
407 pr = prefs()
408 col = self.layout
409 self.draw_header(col)
411 history_num_cols = int(
412 (self.width - POPUP_PADDING) / (ui_scale() * ICON_SIZE))
413 num_cols = min(
414 self.get_num_cols(len(pr.popup_icons.filtered_icons)),
415 history_num_cols)
417 subcol = col.column(align=True)
419 if HISTORY and pr.show_history:
420 pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
422 pr.popup_icons.draw(subcol.box(), num_cols)
424 def close(self):
425 bpy.context.window.screen = bpy.context.window.screen
427 def check(self, context):
428 return True
430 def cancel(self, context):
431 IV_OT_icons_show.instance = None
432 IV_PT_icons.tag_redraw()
434 def execute(self, context):
435 if not IV_OT_icons_show.instance:
436 return {'CANCELLED'}
437 IV_OT_icons_show.instance = None
439 pr = prefs()
440 if self.selected_icon and not pr.copy_on_select:
441 context.window_manager.clipboard = self.selected_icon
442 self.report({'INFO'}, self.selected_icon)
443 pr.popup_icons.selected_icon = ""
445 IV_PT_icons.tag_redraw()
446 return {'FINISHED'}
448 def invoke(self, context, event):
449 pr = prefs()
450 pr.popup_icons.selected_icon = ""
451 pr.popup_icons.filter = ""
452 IV_OT_icons_show.instance = self
453 self.auto_focusable = True
455 num_cols = self.get_num_cols(len(pr.popup_icons.filtered_icons))
456 self.width = int(min(
457 ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
458 context.window.width - WIN_PADDING))
460 return context.window_manager.invoke_props_dialog(
461 self, width=self.width)
463 def draw_console_header(self, context):
464 if not prefs().show_header:
465 return
466 self.layout.operator(IV_OT_icons_show.bl_idname)
468 classes = (
469 IV_PT_icons,
470 IV_OT_panel_menu_call,
471 IV_OT_icon_select,
472 IV_OT_icons_show,
473 IV_Preferences,
477 def register():
478 if bpy.app.background:
479 return
481 for cls in classes:
482 bpy.utils.register_class(cls)
484 bpy.types.CONSOLE_HT_header.append(draw_console_header)
487 def unregister():
488 if bpy.app.background:
489 return
491 bpy.types.CONSOLE_HT_header.remove(draw_console_header)
493 for cls in classes:
494 bpy.utils.unregister_class(cls)