Sun Position: Fix crash when Blender was started in background
[blender-addons.git] / development_icon_get.py
blob1f22a4d8c3e19712bff3fec8baf6b7ec5ad2d8a4
1 # SPDX-License-Identifier: GPL-2.0-or-later
4 bl_info = {
5 "name": "Icon Viewer",
6 "description": "Click an icon to copy its name to the clipboard",
7 "author": "roaoao",
8 "version": (1, 4, 1),
9 "blender": (3, 4, 0),
10 "location": "Text Editor > Dev Tab > Icon Viewer",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/development/icon_viewer.html",
12 "category": "Development",
15 import bpy
16 import math
17 from bpy.props import (
18 BoolProperty,
19 StringProperty,
22 DPI = 72
23 POPUP_PADDING = 10
24 PANEL_PADDING = 44
25 WIN_PADDING = 32
26 ICON_SIZE = 20
27 HISTORY_SIZE = 100
28 HISTORY = []
31 def ui_scale():
32 prefs = bpy.context.preferences.system
33 return prefs.dpi / DPI
36 def prefs():
37 return bpy.context.preferences.addons[__name__].preferences
40 class Icons:
41 def __init__(self, is_popup=False):
42 self._filtered_icons = None
43 self._filter = ""
44 self.filter = ""
45 self.selected_icon = ""
46 self.is_popup = is_popup
48 @property
49 def filter(self):
50 return self._filter
52 @filter.setter
53 def filter(self, value):
54 if self._filter == value:
55 return
57 self._filter = value
58 self.update()
60 @property
61 def filtered_icons(self):
62 if self._filtered_icons is None:
63 self._filtered_icons = []
64 icon_filter = self._filter.upper()
65 self.filtered_icons.clear()
66 pr = prefs()
68 icons = bpy.types.UILayout.bl_rna.functions[
69 "prop"].parameters["icon"].enum_items.keys()
70 for icon in icons:
71 if icon == 'NONE' or \
72 icon_filter and icon_filter not in icon or \
73 not pr.show_brush_icons and "BRUSH_" in icon and \
74 icon != 'BRUSH_DATA' or \
75 not pr.show_matcap_icons and "MATCAP_" in icon or \
76 not pr.show_event_icons and (
77 "EVENT_" in icon or "MOUSE_" in icon
78 ) or \
79 not pr.show_colorset_icons and "COLORSET_" in icon:
80 continue
81 self._filtered_icons.append(icon)
83 return self._filtered_icons
85 @property
86 def num_icons(self):
87 return len(self.filtered_icons)
89 def update(self):
90 if self._filtered_icons is not None:
91 self._filtered_icons.clear()
92 self._filtered_icons = None
94 def draw(self, layout, num_cols=0, icons=None):
95 if icons:
96 filtered_icons = reversed(icons)
97 else:
98 filtered_icons = self.filtered_icons
100 column = layout.column(align=True)
101 row = column.row(align=True)
102 row.alignment = 'CENTER'
104 selected_icon = self.selected_icon if self.is_popup else \
105 bpy.context.window_manager.clipboard
106 col_idx = 0
107 for i, icon in enumerate(filtered_icons):
108 p = row.operator(
109 IV_OT_icon_select.bl_idname, text="",
110 icon=icon, emboss=icon == selected_icon)
111 p.icon = icon
112 p.force_copy_on_select = not self.is_popup
114 col_idx += 1
115 if col_idx > num_cols - 1:
116 if icons:
117 break
118 col_idx = 0
119 if i < len(filtered_icons) - 1:
120 row = column.row(align=True)
121 row.alignment = 'CENTER'
123 if col_idx != 0 and not icons and i >= num_cols:
124 for _ in range(num_cols - col_idx):
125 row.label(text="", icon='BLANK1')
127 if not filtered_icons:
128 row.label(text="No icons were found")
131 class IV_Preferences(bpy.types.AddonPreferences):
132 bl_idname = __name__
134 panel_icons = Icons()
135 popup_icons = Icons(is_popup=True)
137 def update_icons(self, context):
138 self.panel_icons.update()
139 self.popup_icons.update()
141 def set_panel_filter(self, value):
142 self.panel_icons.filter = value
144 panel_filter: StringProperty(
145 description="Filter",
146 default="",
147 get=lambda s: s.panel_icons.filter,
148 set=set_panel_filter,
149 options={'TEXTEDIT_UPDATE'})
150 show_panel_icons: BoolProperty(
151 name="Show Icons",
152 description="Show icons", default=True)
153 show_history: BoolProperty(
154 name="Show History",
155 description="Show history", default=True)
156 show_brush_icons: BoolProperty(
157 name="Show Brush Icons",
158 description="Show brush icons", default=True,
159 update=update_icons)
160 show_matcap_icons: BoolProperty(
161 name="Show Matcap Icons",
162 description="Show matcap icons", default=True,
163 update=update_icons)
164 show_event_icons: BoolProperty(
165 name="Show Event Icons",
166 description="Show event icons", default=True,
167 update=update_icons)
168 show_colorset_icons: BoolProperty(
169 name="Show Colorset Icons",
170 description="Show colorset icons", default=True,
171 update=update_icons)
172 copy_on_select: BoolProperty(
173 name="Copy Icon On Click",
174 description="Copy icon on click", default=True)
175 close_on_select: BoolProperty(
176 name="Close Popup On Click",
177 description=(
178 "Close the popup on click.\n"
179 "Not supported by some windows (User Preferences, Render)"
181 default=False)
182 auto_focus_filter: BoolProperty(
183 name="Auto Focus Input Field",
184 description="Auto focus input field", default=True)
185 show_panel: BoolProperty(
186 name="Show Panel",
187 description="Show the panel in the Text Editor", default=True)
188 show_header: BoolProperty(
189 name="Show Header",
190 description="Show the header in the Python Console",
191 default=True)
193 def draw(self, context):
194 layout = self.layout
195 row = layout.row()
196 row.scale_y = 1.5
197 row.operator(IV_OT_icons_show.bl_idname)
199 row = layout.row()
201 col = row.column(align=True)
202 col.label(text="Icons:")
203 col.prop(self, "show_matcap_icons")
204 col.prop(self, "show_brush_icons")
205 col.prop(self, "show_colorset_icons")
206 col.prop(self, "show_event_icons")
207 col.separator()
208 col.prop(self, "show_history")
210 col = row.column(align=True)
211 col.label(text="Popup:")
212 col.prop(self, "auto_focus_filter")
213 col.prop(self, "copy_on_select")
214 if self.copy_on_select:
215 col.prop(self, "close_on_select")
217 col = row.column(align=True)
218 col.label(text="Panel:")
219 col.prop(self, "show_panel")
220 if self.show_panel:
221 col.prop(self, "show_panel_icons")
223 col.separator()
224 col.label(text="Header:")
225 col.prop(self, "show_header")
228 class IV_PT_icons(bpy.types.Panel):
229 bl_space_type = "TEXT_EDITOR"
230 bl_region_type = "UI"
231 bl_label = "Icon Viewer"
232 bl_category = "Dev"
233 bl_options = {'DEFAULT_CLOSED'}
235 @staticmethod
236 def tag_redraw():
237 wm = bpy.context.window_manager
238 if not wm:
239 return
241 for w in wm.windows:
242 for a in w.screen.areas:
243 if a.type == 'TEXT_EDITOR':
244 for r in a.regions:
245 if r.type == 'UI':
246 r.tag_redraw()
248 def draw(self, context):
249 pr = prefs()
250 row = self.layout.row(align=True)
251 if pr.show_panel_icons:
252 row.prop(pr, "panel_filter", text="", icon='VIEWZOOM')
253 else:
254 row.operator(IV_OT_icons_show.bl_idname)
255 row.operator(
256 IV_OT_panel_menu_call.bl_idname, text="", icon='COLLAPSEMENU')
258 _, y0 = context.region.view2d.region_to_view(0, 0)
259 _, y1 = context.region.view2d.region_to_view(0, 10)
260 region_scale = 10 / abs(y0 - y1)
262 num_cols = max(
264 (context.region.width - PANEL_PADDING) //
265 math.ceil(ui_scale() * region_scale * ICON_SIZE))
267 col = None
268 if HISTORY and pr.show_history:
269 col = self.layout.column(align=True)
270 pr.panel_icons.draw(col.box(), num_cols, HISTORY)
272 if pr.show_panel_icons:
273 col = col or self.layout.column(align=True)
274 pr.panel_icons.draw(col.box(), num_cols)
276 @classmethod
277 def poll(cls, context):
278 return prefs().show_panel
281 class IV_OT_panel_menu_call(bpy.types.Operator):
282 bl_idname = "iv.panel_menu_call"
283 bl_label = ""
284 bl_description = "Menu"
285 bl_options = {'INTERNAL'}
287 def menu(self, menu, context):
288 pr = prefs()
289 layout = menu.layout
290 layout.prop(pr, "show_panel_icons")
291 layout.prop(pr, "show_history")
293 if not pr.show_panel_icons:
294 return
296 layout.separator()
297 layout.prop(pr, "show_matcap_icons")
298 layout.prop(pr, "show_brush_icons")
299 layout.prop(pr, "show_colorset_icons")
300 layout.prop(pr, "show_event_icons")
302 def execute(self, context):
303 context.window_manager.popup_menu(self.menu, title="Icon Viewer")
304 return {'FINISHED'}
307 class IV_OT_icon_select(bpy.types.Operator):
308 bl_idname = "iv.icon_select"
309 bl_label = ""
310 bl_description = "Select the icon"
311 bl_options = {'INTERNAL'}
313 icon: StringProperty()
314 force_copy_on_select: BoolProperty()
316 def execute(self, context):
317 pr = prefs()
318 pr.popup_icons.selected_icon = self.icon
319 if pr.copy_on_select or self.force_copy_on_select:
320 context.window_manager.clipboard = self.icon
321 self.report({'INFO'}, self.icon)
323 if pr.close_on_select and IV_OT_icons_show.instance:
324 IV_OT_icons_show.instance.close()
326 if pr.show_history:
327 if self.icon in HISTORY:
328 HISTORY.remove(self.icon)
329 if len(HISTORY) >= HISTORY_SIZE:
330 HISTORY.pop(0)
331 HISTORY.append(self.icon)
332 return {'FINISHED'}
335 class IV_OT_icons_show(bpy.types.Operator):
336 bl_idname = "iv.icons_show"
337 bl_label = "Icon Viewer"
338 bl_description = "Icon viewer"
339 bl_property = "filter_auto_focus"
341 instance = None
343 def set_filter(self, value):
344 prefs().popup_icons.filter = value
346 def set_selected_icon(self, value):
347 if IV_OT_icons_show.instance:
348 IV_OT_icons_show.instance.auto_focusable = False
350 filter_auto_focus: StringProperty(
351 description="Filter",
352 get=lambda s: prefs().popup_icons.filter,
353 set=set_filter,
354 options={'TEXTEDIT_UPDATE', 'SKIP_SAVE'})
355 filter: StringProperty(
356 description="Filter",
357 get=lambda s: prefs().popup_icons.filter,
358 set=set_filter,
359 options={'TEXTEDIT_UPDATE'})
360 selected_icon: StringProperty(
361 description="Selected Icon",
362 get=lambda s: prefs().popup_icons.selected_icon,
363 set=set_selected_icon)
365 def get_num_cols(self, num_icons):
366 return round(1.3 * math.sqrt(num_icons))
368 def draw_header(self, layout):
369 pr = prefs()
370 header = layout.box()
371 header = header.split(factor=0.75) if self.selected_icon else \
372 header.row()
373 row = header.row(align=True)
374 row.prop(pr, "show_matcap_icons", text="", icon='SHADING_RENDERED')
375 row.prop(pr, "show_brush_icons", text="", icon='BRUSH_DATA')
376 row.prop(pr, "show_colorset_icons", text="", icon='COLOR')
377 row.prop(pr, "show_event_icons", text="", icon='HAND')
378 row.separator()
380 row.prop(
381 pr, "copy_on_select", text="",
382 icon='COPYDOWN', toggle=True)
383 if pr.copy_on_select:
384 sub = row.row(align=True)
385 if bpy.context.window.screen.name == "temp":
386 sub.alert = True
387 sub.prop(
388 pr, "close_on_select", text="",
389 icon='RESTRICT_SELECT_OFF', toggle=True)
390 row.prop(
391 pr, "auto_focus_filter", text="",
392 icon='OUTLINER_DATA_FONT', toggle=True)
393 row.separator()
395 if self.auto_focusable and pr.auto_focus_filter:
396 row.prop(self, "filter_auto_focus", text="", icon='VIEWZOOM')
397 else:
398 row.prop(self, "filter", text="", icon='VIEWZOOM')
400 if self.selected_icon:
401 row = header.row()
402 row.prop(self, "selected_icon", text="", icon=self.selected_icon)
404 def draw(self, context):
405 pr = prefs()
406 col = self.layout
407 self.draw_header(col)
409 history_num_cols = int(
410 (self.width - POPUP_PADDING) / (ui_scale() * ICON_SIZE))
411 num_cols = min(
412 self.get_num_cols(len(pr.popup_icons.filtered_icons)),
413 history_num_cols)
415 subcol = col.column(align=True)
417 if HISTORY and pr.show_history:
418 pr.popup_icons.draw(subcol.box(), history_num_cols, HISTORY)
420 pr.popup_icons.draw(subcol.box(), num_cols)
422 def close(self):
423 bpy.context.window.screen = bpy.context.window.screen
425 def check(self, context):
426 return True
428 def cancel(self, context):
429 IV_OT_icons_show.instance = None
430 IV_PT_icons.tag_redraw()
432 def execute(self, context):
433 if not IV_OT_icons_show.instance:
434 return {'CANCELLED'}
435 IV_OT_icons_show.instance = None
437 pr = prefs()
438 if self.selected_icon and not pr.copy_on_select:
439 context.window_manager.clipboard = self.selected_icon
440 self.report({'INFO'}, self.selected_icon)
441 pr.popup_icons.selected_icon = ""
443 IV_PT_icons.tag_redraw()
444 return {'FINISHED'}
446 def invoke(self, context, event):
447 pr = prefs()
448 pr.popup_icons.selected_icon = ""
449 pr.popup_icons.filter = ""
450 IV_OT_icons_show.instance = self
451 self.auto_focusable = True
453 num_cols = self.get_num_cols(len(pr.popup_icons.filtered_icons))
454 self.width = int(min(
455 ui_scale() * (num_cols * ICON_SIZE + POPUP_PADDING),
456 context.window.width - WIN_PADDING))
458 return context.window_manager.invoke_props_dialog(
459 self, width=self.width)
461 def draw_console_header(self, context):
462 if not prefs().show_header:
463 return
464 self.layout.operator(IV_OT_icons_show.bl_idname)
466 classes = (
467 IV_PT_icons,
468 IV_OT_panel_menu_call,
469 IV_OT_icon_select,
470 IV_OT_icons_show,
471 IV_Preferences,
475 def register():
476 if bpy.app.background:
477 return
479 for cls in classes:
480 bpy.utils.register_class(cls)
482 bpy.types.CONSOLE_HT_header.append(draw_console_header)
485 def unregister():
486 if bpy.app.background:
487 return
489 bpy.types.CONSOLE_HT_header.remove(draw_console_header)
491 for cls in classes:
492 bpy.utils.unregister_class(cls)