Merge branch 'blender-v2.92-release'
[blender-addons.git] / development_icon_get.py
blobe8901eca174e4e78ecfebeea618862a0c3f91db9
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
22 bl_info = {
23 "name": "Icon Viewer",
24 "description": "Click an icon to copy its name to the clipboard",
25 "author": "roaoao",
26 "version": (1, 4, 0),
27 "blender": (2, 80, 0),
28 "location": "Text Editor > Dev Tab > Icon Viewer",
29 "doc_url": "{BLENDER_MANUAL_URL}/addons/development/icon_viewer.html",
30 "category": "Development",
33 import bpy
34 import math
35 from bpy.props import (
36 BoolProperty,
37 StringProperty,
40 DPI = 72
41 POPUP_PADDING = 10
42 PANEL_PADDING = 44
43 WIN_PADDING = 32
44 ICON_SIZE = 20
45 HISTORY_SIZE = 100
46 HISTORY = []
49 def ui_scale():
50 prefs = bpy.context.preferences.system
51 return prefs.dpi * prefs.pixel_size / DPI
54 def prefs():
55 return bpy.context.preferences.addons[__name__].preferences
58 class Icons:
59 def __init__(self, is_popup=False):
60 self._filtered_icons = None
61 self._filter = ""
62 self.filter = ""
63 self.selected_icon = ""
64 self.is_popup = is_popup
66 @property
67 def filter(self):
68 return self._filter
70 @filter.setter
71 def filter(self, value):
72 if self._filter == value:
73 return
75 self._filter = value
76 self.update()
78 @property
79 def filtered_icons(self):
80 if self._filtered_icons is None:
81 self._filtered_icons = []
82 icon_filter = self._filter.upper()
83 self.filtered_icons.clear()
84 pr = prefs()
86 icons = bpy.types.UILayout.bl_rna.functions[
87 "prop"].parameters["icon"].enum_items.keys()
88 for icon in icons:
89 if icon == 'NONE' or \
90 icon_filter and icon_filter not in icon or \
91 not pr.show_brush_icons and "BRUSH_" in icon and \
92 icon != 'BRUSH_DATA' or \
93 not pr.show_matcap_icons and "MATCAP_" in icon or \
94 not pr.show_event_icons and (
95 "EVENT_" in icon or "MOUSE_" in icon
96 ) or \
97 not pr.show_colorset_icons and "COLORSET_" in icon:
98 continue
99 self._filtered_icons.append(icon)
101 return self._filtered_icons
103 @property
104 def num_icons(self):
105 return len(self.filtered_icons)
107 def update(self):
108 if self._filtered_icons is not None:
109 self._filtered_icons.clear()
110 self._filtered_icons = None
112 def draw(self, layout, num_cols=0, icons=None):
113 if icons:
114 filtered_icons = reversed(icons)
115 else:
116 filtered_icons = self.filtered_icons
118 column = layout.column(align=True)
119 row = column.row(align=True)
120 row.alignment = 'CENTER'
122 selected_icon = self.selected_icon if self.is_popup else \
123 bpy.context.window_manager.clipboard
124 col_idx = 0
125 for i, icon in enumerate(filtered_icons):
126 p = row.operator(
127 IV_OT_icon_select.bl_idname, text="",
128 icon=icon, emboss=icon == selected_icon)
129 p.icon = icon
130 p.force_copy_on_select = not self.is_popup
132 col_idx += 1
133 if col_idx > num_cols - 1:
134 if icons:
135 break
136 col_idx = 0
137 if i < len(filtered_icons) - 1:
138 row = column.row(align=True)
139 row.alignment = 'CENTER'
141 if col_idx != 0 and not icons and i >= num_cols:
142 for _ in range(num_cols - col_idx):
143 row.label(text="", icon='BLANK1')
145 if not filtered_icons:
146 row.label(text="No icons were found")
149 class IV_Preferences(bpy.types.AddonPreferences):
150 bl_idname = __name__
152 panel_icons = Icons()
153 popup_icons = Icons(is_popup=True)
155 def update_icons(self, context):
156 self.panel_icons.update()
157 self.popup_icons.update()
159 def set_panel_filter(self, value):
160 self.panel_icons.filter = value
162 panel_filter: StringProperty(
163 description="Filter",
164 default="",
165 get=lambda s: s.panel_icons.filter,
166 set=set_panel_filter,
167 options={'TEXTEDIT_UPDATE'})
168 show_panel_icons: BoolProperty(
169 name="Show Icons",
170 description="Show icons", default=True)
171 show_history: BoolProperty(
172 name="Show History",
173 description="Show history", default=True)
174 show_brush_icons: BoolProperty(
175 name="Show Brush Icons",
176 description="Show brush icons", default=True,
177 update=update_icons)
178 show_matcap_icons: BoolProperty(
179 name="Show Matcap Icons",
180 description="Show matcap icons", default=True,
181 update=update_icons)
182 show_event_icons: BoolProperty(
183 name="Show Event Icons",
184 description="Show event icons", default=True,
185 update=update_icons)
186 show_colorset_icons: BoolProperty(
187 name="Show Colorset Icons",
188 description="Show colorset icons", default=True,
189 update=update_icons)
190 copy_on_select: BoolProperty(
191 name="Copy Icon On Click",
192 description="Copy icon on click", default=True)
193 close_on_select: BoolProperty(
194 name="Close Popup On Click",
195 description=(
196 "Close the popup on click.\n"
197 "Not supported by some windows (User Preferences, Render)"
199 default=False)
200 auto_focus_filter: BoolProperty(
201 name="Auto Focus Input Field",
202 description="Auto focus input field", default=True)
203 show_panel: BoolProperty(
204 name="Show Panel",
205 description="Show the panel in the Text Editor", default=True)
206 show_header: BoolProperty(
207 name="Show Header",
208 description="Show the header in the Python Console",
209 default=True)
211 def draw(self, context):
212 layout = self.layout
213 row = layout.row()
214 row.scale_y = 1.5
215 row.operator(IV_OT_icons_show.bl_idname)
217 row = layout.row()
219 col = row.column(align=True)
220 col.label(text="Icons:")
221 col.prop(self, "show_matcap_icons")
222 col.prop(self, "show_brush_icons")
223 col.prop(self, "show_colorset_icons")
224 col.prop(self, "show_event_icons")
225 col.separator()
226 col.prop(self, "show_history")
228 col = row.column(align=True)
229 col.label(text="Popup:")
230 col.prop(self, "auto_focus_filter")
231 col.prop(self, "copy_on_select")
232 if self.copy_on_select:
233 col.prop(self, "close_on_select")
235 col = row.column(align=True)
236 col.label(text="Panel:")
237 col.prop(self, "show_panel")
238 if self.show_panel:
239 col.prop(self, "show_panel_icons")
241 col.separator()
242 col.label(text="Header:")
243 col.prop(self, "show_header")
246 class IV_PT_icons(bpy.types.Panel):
247 bl_space_type = "TEXT_EDITOR"
248 bl_region_type = "UI"
249 bl_label = "Icon Viewer"
250 bl_category = "Dev"
251 bl_options = {'DEFAULT_CLOSED'}
253 @staticmethod
254 def tag_redraw():
255 wm = bpy.context.window_manager
256 if not wm:
257 return
259 for w in wm.windows:
260 for a in w.screen.areas:
261 if a.type == 'TEXT_EDITOR':
262 for r in a.regions:
263 if r.type == 'UI':
264 r.tag_redraw()
266 def draw(self, context):
267 pr = prefs()
268 row = self.layout.row(align=True)
269 if pr.show_panel_icons:
270 row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
271 else:
272 row.operator(IV_OT_icons_show.bl_idname)
273 row.operator(
274 IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
276 _, y0 = context.region.view2d.region_to_view(0, 0)
277 _, y1 = context.region.view2d.region_to_view(0, 10)
278 region_scale = 10 / abs(y0 - y1)
280 num_cols = max(
282 (context.region.width - PANEL_PADDING) //
283 math.ceil(ui_scale() * region_scale * ICON_SIZE))
285 col = None
286 if HISTORY and pr.show_history:
287 col = self.layout.column(align=True)
288 pr.panel_icons.draw(col.box(), num_cols, HISTORY)
290 if pr.show_panel_icons:
291 col = col or self.layout.column(align=True)
292 pr.panel_icons.draw(col.box(), num_cols)
294 @classmethod
295 def poll(cls, context):
296 return prefs().show_panel
299 class IV_OT_panel_menu_call(bpy.types.Operator):
300 bl_idname = "iv.panel_menu_call"
301 bl_label = ""
302 bl_description = "Menu"
303 bl_options = {'INTERNAL'}
305 def menu(self, menu, context):
306 pr = prefs()
307 layout = menu.layout
308 layout.prop(pr, "show_panel_icons")
309 layout.prop(pr, "show_history")
311 if not pr.show_panel_icons:
312 return
314 layout.separator()
315 layout.prop(pr, "show_matcap_icons")
316 layout.prop(pr, "show_brush_icons")
317 layout.prop(pr, "show_colorset_icons")
318 layout.prop(pr, "show_event_icons")
320 def execute(self, context):
321 context.window_manager.popup_menu(self.menu, title="Icon Viewer")
322 return {'FINISHED'}
325 class IV_OT_icon_select(bpy.types.Operator):
326 bl_idname = "iv.icon_select"
327 bl_label = ""
328 bl_description = "Select the icon"
329 bl_options = {'INTERNAL'}
331 icon: StringProperty()
332 force_copy_on_select: BoolProperty()
334 def execute(self, context):
335 pr = prefs()
336 pr.popup_icons.selected_icon = self.icon
337 if pr.copy_on_select or self.force_copy_on_select:
338 context.window_manager.clipboard = self.icon
339 self.report({'INFO'}, self.icon)
341 if pr.close_on_select and IV_OT_icons_show.instance:
342 IV_OT_icons_show.instance.close()
344 if pr.show_history:
345 if self.icon in HISTORY:
346 HISTORY.remove(self.icon)
347 if len(HISTORY) >= HISTORY_SIZE:
348 HISTORY.pop(0)
349 HISTORY.append(self.icon)
350 return {'FINISHED'}
353 class IV_OT_icons_show(bpy.types.Operator):
354 bl_idname = "iv.icons_show"
355 bl_label = "Icon Viewer"
356 bl_description = "Icon viewer"
357 bl_property = "filter_auto_focus"
359 instance = None
361 def set_filter(self, value):
362 prefs().popup_icons.filter = value
364 def set_selected_icon(self, value):
365 if IV_OT_icons_show.instance:
366 IV_OT_icons_show.instance.auto_focusable = False
368 filter_auto_focus: StringProperty(
369 description="Filter",
370 get=lambda s: prefs().popup_icons.filter,
371 set=set_filter,
372 options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
373 filter: StringProperty(
374 description="Filter",
375 get=lambda s: prefs().popup_icons.filter,
376 set=set_filter,
377 options={'TEXTEDIT_UPDATE'})
378 selected_icon: StringProperty(
379 description="Selected Icon",
380 get=lambda s: prefs().popup_icons.selected_icon,
381 set=set_selected_icon)
383 def get_num_cols(self, num_icons):
384 return round(1.3 * math.sqrt(num_icons))
386 def draw_header(self, layout):
387 pr = prefs()
388 header = layout.box()
389 header = header.split(factor=0.75) if self.selected_icon else \
390 header.row()
391 row = header.row(align=True)
392 row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
393 row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
394 row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
395 row.prop(pr, "show_event_icons", text="", icon='HAND')
396 row.separator()
398 row.prop(
399 pr, "copy_on_select", text="",
400 icon='COPYDOWN', toggle=True)
401 if pr.copy_on_select:
402 sub = row.row(align=True)
403 if bpy.context.window.screen.name == "temp":
404 sub.alert = True
405 sub.prop(
406 pr, "close_on_select", text="",
407 icon='RESTRICT_SELECT_OFF', toggle=True)
408 row.prop(
409 pr, "auto_focus_filter", text="",
410 icon='OUTLINER_DATA_FONT', toggle=True)
411 row.separator()
413 if self.auto_focusable and pr.auto_focus_filter:
414 row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
415 else:
416 row.prop(self, "filter", text="", icon='VIEWZOOM')
418 if self.selected_icon:
419 row = header.row()
420 row.prop(self, "selected_icon", text="", icon=self.selected_icon)
422 def draw(self, context):
423 pr = prefs()
424 col = self.layout
425 self.draw_header(col)
427 history_num_cols = int(
428 (self.width - POPUP_PADDING) / (ui_scale() * ICON_SIZE))
429 num_cols = min(
430 self.get_num_cols(len(pr.popup_icons.filtered_icons)),
431 history_num_cols)
433 subcol = col.column(align=True)
435 if HISTORY and pr.show_history:
436 pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
438 pr.popup_icons.draw(subcol.box(), num_cols)
440 def close(self):
441 bpy.context.window.screen = bpy.context.window.screen
443 def check(self, context):
444 return True
446 def cancel(self, context):
447 IV_OT_icons_show.instance = None
448 IV_PT_icons.tag_redraw()
450 def execute(self, context):
451 if not IV_OT_icons_show.instance:
452 return {'CANCELLED'}
453 IV_OT_icons_show.instance = None
455 pr = prefs()
456 if self.selected_icon and not pr.copy_on_select:
457 context.window_manager.clipboard = self.selected_icon
458 self.report({'INFO'}, self.selected_icon)
459 pr.popup_icons.selected_icon = ""
461 IV_PT_icons.tag_redraw()
462 return {'FINISHED'}
464 def invoke(self, context, event):
465 pr = prefs()
466 pr.popup_icons.selected_icon = ""
467 pr.popup_icons.filter = ""
468 IV_OT_icons_show.instance = self
469 self.auto_focusable = True
471 num_cols = self.get_num_cols(len(pr.popup_icons.filtered_icons))
472 self.width = min(
473 ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
474 context.window.width - WIN_PADDING)
476 return context.window_manager.invoke_props_dialog(
477 self, width=self.width)
479 def draw_console_header(self, context):
480 if not prefs().show_header:
481 return
482 self.layout.operator(IV_OT_icons_show.bl_idname)
484 classes = (
485 IV_PT_icons,
486 IV_OT_panel_menu_call,
487 IV_OT_icon_select,
488 IV_OT_icons_show,
489 IV_Preferences,
493 def register():
494 if bpy.app.background:
495 return
497 for cls in classes:
498 bpy.utils.register_class(cls)
500 bpy.types.CONSOLE_HT_header.append(draw_console_header)
503 def unregister():
504 if bpy.app.background:
505 return
507 bpy.types.CONSOLE_HT_header.remove(draw_console_header)
509 for cls in classes:
510 bpy.utils.unregister_class(cls)