Fix T59445: top bar hides everything when there isn't enough space.
[blender.git] / release / scripts / startup / bl_ui / space_topbar.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20 import bpy
21 from bpy.types import Header, Menu, Panel
22 from .properties_grease_pencil_common import (
23     GPENCIL_UL_layer,
24 )
25
26
27 class TOPBAR_HT_upper_bar(Header):
28     bl_space_type = 'TOPBAR'
29
30     def draw(self, context):
31         region = context.region
32
33         if region.alignment == 'RIGHT':
34             self.draw_right(context)
35         else:
36             self.draw_left(context)
37
38     def draw_left(self, context):
39         layout = self.layout
40
41         window = context.window
42         screen = context.screen
43
44         layout.operator("wm.splash", text="", icon='BLENDER', emboss=False)
45
46         TOPBAR_MT_editor_menus.draw_collapsible(context, layout)
47
48         layout.separator()
49
50         if not screen.show_fullscreen:
51             layout.template_ID_tabs(
52                 window, "workspace",
53                 new="workspace.add",
54                 menu="TOPBAR_MT_workspace_menu",
55             )
56         else:
57             layout.operator(
58                 "screen.back_to_previous",
59                 icon='SCREEN_BACK',
60                 text="Back to Previous",
61             )
62
63     def draw_right(self, context):
64         layout = self.layout
65
66         window = context.window
67         screen = context.screen
68         scene = window.scene
69
70         # If statusbar is hidden, still show messages at the top
71         if not screen.show_statusbar:
72             layout.template_reports_banner()
73             layout.template_running_jobs()
74
75         # Active workspace view-layer is retrieved through window, not through workspace.
76         layout.template_ID(window, "scene", new="scene.new", unlink="scene.delete")
77
78         row = layout.row(align=True)
79         row.template_search(
80             window, "view_layer",
81             scene, "view_layers",
82             new="scene.view_layer_add",
83             unlink="scene.view_layer_remove")
84
85
86 class TOPBAR_HT_lower_bar(Header):
87     bl_space_type = 'TOPBAR'
88     bl_region_type = 'WINDOW'
89
90     def draw(self, context):
91         region = context.region
92
93         if region.alignment == 'RIGHT':
94             self.draw_right(context)
95         else:
96             self.draw_left(context)
97
98     def draw_left(self, context):
99         layout = self.layout
100
101         # Active Tool
102         # -----------
103         from .space_toolsystem_common import ToolSelectPanelHelper
104         tool = ToolSelectPanelHelper.draw_active_tool_header(context, layout)
105         tool_space_type = 'VIEW_3D' if tool is None else tool.space_type
106         tool_mode = context.mode if tool is None else tool.mode
107
108         # Object Mode Options
109         # -------------------
110
111         # Example of how tool_settings can be accessed as pop-overs.
112
113         # TODO(campbell): editing options should be after active tool options
114         # (obviously separated for from the users POV)
115         draw_fn = getattr(getattr(_draw_left_context_mode, tool_space_type, None), tool_mode, None)
116         if draw_fn is not None:
117             draw_fn(context, layout, tool)
118
119         if tool_space_type == 'VIEW_3D':
120             # Note: general mode options should be added to 'draw_right'.
121             if tool_mode == 'SCULPT':
122                 if (tool is not None) and tool.has_datablock:
123                     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
124             elif tool_mode == 'PAINT_VERTEX':
125                 if (tool is not None) and tool.has_datablock:
126                     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
127             elif tool_mode == 'PAINT_WEIGHT':
128                 if (tool is not None) and tool.has_datablock:
129                     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
130             elif tool_mode == 'PAINT_TEXTURE':
131                 if (tool is not None) and tool.has_datablock:
132                     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
133             elif tool_mode == 'EDIT_ARMATURE':
134                 pass
135             elif tool_mode == 'EDIT_CURVE':
136                 pass
137             elif tool_mode == 'EDIT_MESH':
138                 pass
139             elif tool_mode == 'POSE':
140                 pass
141             elif tool_mode == 'PARTICLE':
142                 # Disable, only shows "Brush" panel, which is already in the top-bar.
143                 # if tool.has_datablock:
144                 #     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common", category="")
145                 pass
146             elif tool_mode == 'PAINT_GPENCIL':
147                 if (tool is not None) and tool.has_datablock:
148                     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".greasepencil_paint", category="")
149             elif tool_mode == 'SCULPT_GPENCIL':
150                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".greasepencil_sculpt", category="")
151             elif tool_mode == 'WEIGHT_GPENCIL':
152                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".greasepencil_weight", category="")
153         elif tool_space_type == 'IMAGE_EDITOR':
154             if tool_mode == 'PAINT':
155                 if (tool is not None) and tool.has_datablock:
156                     layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".paint_common_2d", category="")
157             elif context.uv_sculpt_object is not None:
158                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".uv_sculpt", category="")
159
160     def draw_right(self, context):
161         layout = self.layout
162
163         # Active Tool
164         # -----------
165         from .space_toolsystem_common import ToolSelectPanelHelper
166         tool = ToolSelectPanelHelper.tool_active_from_context(context)
167         tool_space_type = 'VIEW_3D' if tool is None else tool.space_type
168         tool_mode = context.mode if tool is None else tool.mode
169
170         if tool_space_type == 'VIEW_3D':
171             if tool_mode == 'SCULPT':
172                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".sculpt_mode", category="")
173             elif tool_mode == 'PAINT_VERTEX':
174                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".vertexpaint", category="")
175             elif tool_mode == 'PAINT_WEIGHT':
176                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".weightpaint", category="")
177             elif tool_mode == 'PAINT_TEXTURE':
178                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".imagepaint", category="")
179             elif tool_mode == 'EDIT_TEXT':
180                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".text_edit", category="")
181             elif tool_mode == 'EDIT_ARMATURE':
182                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".armature_edit", category="")
183             elif tool_mode == 'EDIT_METABALL':
184                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".mball_edit", category="")
185             elif tool_mode == 'EDIT_LATTICE':
186                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".lattice_edit", category="")
187             elif tool_mode == 'EDIT_CURVE':
188                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".curve_edit", category="")
189             elif tool_mode == 'EDIT_MESH':
190                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".mesh_edit", category="")
191             elif tool_mode == 'POSE':
192                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".posemode", category="")
193             elif tool_mode == 'PARTICLE':
194                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".particlemode", category="")
195             elif tool_mode == 'OBJECT':
196                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".objectmode", category="")
197             elif tool_mode in {'PAINT_GPENCIL', 'EDIT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL'}:
198                 # Grease pencil layer.
199                 gpl = context.active_gpencil_layer
200                 if gpl and gpl.info is not None:
201                     text = gpl.info
202                     maxw = 25
203                     if len(text) > maxw:
204                         text = text[:maxw - 5] + '..' + text[-3:]
205                 else:
206                     text = ""
207
208                 layout.label(text="Layer:")
209                 sub = layout.row()
210                 sub.ui_units_x = 8
211                 sub.popover(
212                     panel="TOPBAR_PT_gpencil_layers",
213                     text=text,
214                 )
215         elif tool_space_type == 'IMAGE_EDITOR':
216             if tool_mode == 'PAINT':
217                 layout.popover_group(space_type='PROPERTIES', region_type='WINDOW', context=".imagepaint_2d", category="")
218
219
220 class _draw_left_context_mode:
221     class VIEW_3D:
222         @staticmethod
223         def SCULPT(context, layout, tool):
224             if (tool is None) or (not tool.has_datablock):
225                 return
226
227             paint = context.tool_settings.sculpt
228             layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
229
230             brush = paint.brush
231             if brush is None:
232                 return
233
234             from .properties_paint_common import (
235                 brush_basic_sculpt_settings,
236             )
237             brush_basic_sculpt_settings(layout, context, brush, compact=True)
238
239         @staticmethod
240         def PAINT_TEXTURE(context, layout, tool):
241             if (tool is None) or (not tool.has_datablock):
242                 return
243
244             paint = context.tool_settings.image_paint
245             layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
246
247             brush = paint.brush
248             if brush is None:
249                 return
250
251             from .properties_paint_common import (
252                 UnifiedPaintPanel,
253                 brush_basic_texpaint_settings,
254             )
255             capabilities = brush.image_paint_capabilities
256             if capabilities.has_color:
257                 UnifiedPaintPanel.prop_unified_color(layout, context, brush, "color", text="")
258             brush_basic_texpaint_settings(layout, context, brush, compact=True)
259
260         @staticmethod
261         def PAINT_VERTEX(context, layout, tool):
262             if (tool is None) or (not tool.has_datablock):
263                 return
264
265             paint = context.tool_settings.vertex_paint
266             layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
267
268             brush = paint.brush
269             if brush is None:
270                 return
271
272             from .properties_paint_common import (
273                 UnifiedPaintPanel,
274                 brush_basic_vpaint_settings,
275             )
276             capabilities = brush.vertex_paint_capabilities
277             if capabilities.has_color:
278                 UnifiedPaintPanel.prop_unified_color(layout, context, brush, "color", text="")
279             brush_basic_vpaint_settings(layout, context, brush, compact=True)
280
281         @staticmethod
282         def PAINT_WEIGHT(context, layout, tool):
283             if (tool is None) or (not tool.has_datablock):
284                 return
285
286             paint = context.tool_settings.weight_paint
287             layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
288             brush = paint.brush
289             if brush is None:
290                 return
291
292             from .properties_paint_common import brush_basic_wpaint_settings
293             brush_basic_wpaint_settings(layout, context, brush, compact=True)
294
295         @staticmethod
296         def PAINT_GPENCIL(context, layout, tool):
297             if tool is None:
298                 return
299
300             is_paint = True
301             if tool.name in {"Line", "Box", "Circle", "Arc", "Curve"}:
302                 is_paint = False
303             elif tool.name == "Cutter":
304                 row = layout.row(align=True)
305                 row.prop(context.tool_settings.gpencil_sculpt, "intersection_threshold")
306                 return
307             elif not tool.has_datablock:
308                 return
309
310             paint = context.tool_settings.gpencil_paint
311             brush = paint.brush
312             if brush is None:
313                 return
314
315             gp_settings = brush.gpencil_settings
316
317             def draw_color_selector():
318                 ma = gp_settings.material
319                 row = layout.row(align=True)
320
321                 icon_id = 0
322                 if ma:
323                     icon_id = ma.id_data.preview.icon_id
324                     txt_ma = ma.name
325                     maxw = 25
326                     if len(txt_ma) > maxw:
327                         txt_ma = txt_ma[:maxw - 5] + '..' + txt_ma[-3:]
328                 else:
329                     txt_ma = ""
330
331                 row.label(text="Material:")
332                 sub = row.row()
333                 sub.ui_units_x = 8
334                 sub.popover(
335                     panel="TOPBAR_PT_gpencil_materials",
336                     text=txt_ma,
337                     icon_value=icon_id,
338                 )
339
340                 row.prop(gp_settings, "use_material_pin", text="")
341
342             row = layout.row(align=True)
343             tool_settings = context.scene.tool_settings
344             settings = tool_settings.gpencil_paint
345             row.template_ID_preview(settings, "brush", rows=3, cols=8, hide_buttons=True)
346
347             if brush.gpencil_tool in {'FILL', 'DRAW'}:
348                 draw_color_selector()
349
350             from .properties_paint_common import (
351                 brush_basic_gpencil_paint_settings,
352             )
353             brush_basic_gpencil_paint_settings(layout, context, brush, compact=True)
354
355             if tool.name in {"Arc", "Curve", "Line", "Box", "Circle"}:
356                 settings = context.tool_settings.gpencil_sculpt
357                 row = layout.row(align=True)
358                 row.prop(settings, "use_thickness_curve", text="", icon='CURVE_DATA')
359                 sub = row.row(align=True)
360                 sub.active = settings.use_thickness_curve
361                 sub.popover(
362                     panel="TOPBAR_PT_gpencil_primitive",
363                     text="Thickness Profile"
364                 )
365
366         @staticmethod
367         def SCULPT_GPENCIL(context, layout, tool):
368             if (tool is None) or (not tool.has_datablock):
369                 return
370             tool_settings = context.tool_settings
371             settings = tool_settings.gpencil_sculpt
372             brush = settings.brush
373
374             from .properties_paint_common import (
375                 brush_basic_gpencil_sculpt_settings,
376             )
377             brush_basic_gpencil_sculpt_settings(layout, context, brush, compact=True)
378
379         @staticmethod
380         def WEIGHT_GPENCIL(context, layout, tool):
381             if (tool is None) or (not tool.has_datablock):
382                 return
383             tool_settings = context.tool_settings
384             settings = tool_settings.gpencil_sculpt
385             brush = settings.brush
386
387             from .properties_paint_common import (
388                 brush_basic_gpencil_weight_settings,
389             )
390             brush_basic_gpencil_weight_settings(layout, context, brush, compact=True)
391
392         @staticmethod
393         def PARTICLE(context, layout, tool):
394             if (tool is None) or (not tool.has_datablock):
395                 return
396
397             # See: 'VIEW3D_PT_tools_brush', basically a duplicate
398             settings = context.tool_settings.particle_edit
399             brush = settings.brush
400             tool = settings.tool
401             if tool != 'NONE':
402                 layout.prop(brush, "size", slider=True)
403                 if tool == 'ADD':
404                     layout.prop(brush, "count")
405
406                     layout.prop(settings, "use_default_interpolate")
407                     layout.prop(brush, "steps", slider=True)
408                     layout.prop(settings, "default_key_count", slider=True)
409                 else:
410                     layout.prop(brush, "strength", slider=True)
411
412                     if tool == 'LENGTH':
413                         layout.row().prop(brush, "length_mode", expand=True)
414                     elif tool == 'PUFF':
415                         layout.row().prop(brush, "puff_mode", expand=True)
416                         layout.prop(brush, "use_puff_volume")
417                     elif tool == 'COMB':
418                         # Note: actually in 'Options' panel,
419                         # disabled when used in popover.
420                         row = layout.row()
421                         row.active = settings.is_editable
422                         row.prop(settings, "use_emitter_deflect", text="Deflect Emitter")
423                         sub = row.row(align=True)
424                         sub.active = settings.use_emitter_deflect
425                         sub.prop(settings, "emitter_distance", text="Distance")
426
427     class IMAGE_EDITOR:
428         @staticmethod
429         def VIEW(context, layout, tool):
430             tool_settings = context.tool_settings
431             if tool_settings.use_uv_sculpt:
432                 if context.mode == 'EDIT_MESH':
433                     uv_sculpt = tool_settings.uv_sculpt
434                     brush = uv_sculpt.brush
435                     if brush:
436                         from .properties_paint_common import UnifiedPaintPanel
437
438                         row = layout.row(align=True)
439                         UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
440                         UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
441
442                         row = layout.row(align=True)
443                         UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength", slider=True)
444                         UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
445
446         @staticmethod
447         def PAINT(context, layout, tool):
448             if (tool is None) or (not tool.has_datablock):
449                 return
450
451             paint = context.tool_settings.image_paint
452             layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
453
454             brush = paint.brush
455             if brush is None:
456                 return
457
458             from .properties_paint_common import (
459                 UnifiedPaintPanel,
460                 brush_basic_texpaint_settings,
461             )
462             capabilities = brush.image_paint_capabilities
463             if capabilities.has_color:
464                 UnifiedPaintPanel.prop_unified_color(layout, context, brush, "color", text="")
465             brush_basic_texpaint_settings(layout, context, brush, compact=True)
466
467
468 class TOPBAR_PT_gpencil_layers(Panel):
469     bl_space_type = 'VIEW_3D'
470     bl_region_type = 'HEADER'
471     bl_label = "Layers"
472     bl_ui_units_x = 14
473
474     @classmethod
475     def poll(cls, context):
476         if context.gpencil_data is None:
477             return False
478
479         ob = context.object
480         if ob is not None and ob.type == 'GPENCIL':
481             return True
482
483         return False
484
485     def draw(self, context):
486         layout = self.layout
487         gpd = context.gpencil_data
488
489         # Grease Pencil data...
490         if (gpd is None) or (not gpd.layers):
491             layout.operator("gpencil.layer_add", text="New Layer")
492         else:
493             self.draw_layers(context, layout, gpd)
494
495     def draw_layers(self, context, layout, gpd):
496         row = layout.row()
497
498         col = row.column()
499         layer_rows = 10
500         col.template_list("GPENCIL_UL_layer", "", gpd, "layers", gpd.layers, "active_index",
501                           rows=layer_rows, sort_reverse=True, sort_lock=True)
502
503         gpl = context.active_gpencil_layer
504         if gpl:
505             srow = col.row(align=True)
506             srow.prop(gpl, "blend_mode", text="Blend")
507
508             srow = col.row(align=True)
509             srow.prop(gpl, "opacity", text="Opacity", slider=True)
510             srow.prop(gpl, "clamp_layer", text="",
511                       icon='MOD_MASK' if gpl.clamp_layer else 'LAYER_ACTIVE')
512
513             srow = col.row(align=True)
514             srow.prop(gpl, "use_solo_mode", text="Show Only On Keyframed")
515
516         col = row.column()
517
518         sub = col.column(align=True)
519         sub.operator("gpencil.layer_add", icon='ADD', text="")
520         sub.operator("gpencil.layer_remove", icon='REMOVE', text="")
521
522         gpl = context.active_gpencil_layer
523         if gpl:
524             sub.menu("GPENCIL_MT_layer_context_menu", icon='DOWNARROW_HLT', text="")
525
526             if len(gpd.layers) > 1:
527                 col.separator()
528
529                 sub = col.column(align=True)
530                 sub.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
531                 sub.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
532
533                 col.separator()
534
535                 sub = col.column(align=True)
536                 sub.operator("gpencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False
537                 sub.operator("gpencil.layer_isolate", icon='HIDE_OFF', text="").affect_visibility = True
538
539
540 class TOPBAR_MT_editor_menus(Menu):
541     bl_idname = "TOPBAR_MT_editor_menus"
542     bl_label = ""
543
544     def draw(self, context):
545         layout = self.layout
546         layout.menu("TOPBAR_MT_file")
547         layout.menu("TOPBAR_MT_edit")
548
549         layout.menu("TOPBAR_MT_render")
550
551         layout.menu("TOPBAR_MT_window")
552         layout.menu("TOPBAR_MT_help")
553
554
555 class TOPBAR_MT_file(Menu):
556     bl_label = "File"
557
558     def draw(self, context):
559         layout = self.layout
560
561         layout.operator_context = 'INVOKE_AREA'
562         layout.menu("TOPBAR_MT_file_new", text="New", icon='FILE_NEW')
563         layout.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
564         layout.menu("TOPBAR_MT_file_open_recent")
565         layout.operator("wm.revert_mainfile")
566         layout.operator("wm.recover_last_session")
567         layout.operator("wm.recover_auto_save", text="Recover Auto Save...")
568
569         layout.separator()
570
571         layout.operator_context = 'EXEC_AREA' if context.blend_data.is_saved else 'INVOKE_AREA'
572         layout.operator("wm.save_mainfile", text="Save", icon='FILE_TICK')
573
574         layout.operator_context = 'INVOKE_AREA'
575         layout.operator("wm.save_as_mainfile", text="Save As...")
576         layout.operator_context = 'INVOKE_AREA'
577         layout.operator("wm.save_as_mainfile", text="Save Copy...").copy = True
578
579         layout.separator()
580         layout.operator_context = 'INVOKE_AREA'
581
582         if any(bpy.utils.app_template_paths()):
583             app_template = context.preferences.app_template
584         else:
585             app_template = None
586
587         if app_template:
588             layout.label(text=bpy.path.display_name(app_template, has_ext=False))
589             layout.operator("wm.save_homefile")
590             layout.operator(
591                 "wm.read_factory_settings",
592                 text="Load Factory Settings",
593             ).app_template = app_template
594         else:
595             layout.operator("wm.save_homefile")
596             layout.operator("wm.read_factory_settings")
597
598         layout.separator()
599
600         layout.operator("preferences.app_template_install", text="Install Application Template...")
601
602         layout.separator()
603
604         layout.operator_context = 'INVOKE_AREA'
605         layout.operator("wm.link", text="Link...", icon='LINK_BLEND')
606         layout.operator("wm.append", text="Append...", icon='APPEND_BLEND')
607         layout.menu("TOPBAR_MT_file_previews")
608
609         layout.separator()
610
611         layout.menu("TOPBAR_MT_file_import", icon='IMPORT')
612         layout.menu("TOPBAR_MT_file_export", icon='EXPORT')
613
614         layout.separator()
615
616         layout.menu("TOPBAR_MT_file_external_data")
617
618         layout.separator()
619
620         layout.operator_context = 'EXEC_AREA'
621         if bpy.data.is_dirty:
622             layout.operator_context = 'INVOKE_SCREEN'  # quit dialog
623         layout.operator("wm.quit_blender", text="Quit", icon='QUIT')
624
625
626 class TOPBAR_MT_file_new(Menu):
627     bl_label = "New File"
628
629     @staticmethod
630     def app_template_paths():
631         import os
632
633         template_paths = bpy.utils.app_template_paths()
634
635         # expand template paths
636         app_templates = []
637         for path in template_paths:
638             for d in os.listdir(path):
639                 if d.startswith(("__", ".")):
640                     continue
641                 template = os.path.join(path, d)
642                 if os.path.isdir(template):
643                     # template_paths_expand.append(template)
644                     app_templates.append(d)
645
646         return sorted(app_templates)
647
648     def draw_ex(layout, context, *, use_splash=False, use_more=False):
649         layout.operator_context = 'EXEC_DEFAULT'
650
651         # Limit number of templates in splash screen, spill over into more menu.
652         paths = TOPBAR_MT_file_new.app_template_paths()
653         splash_limit = 5
654
655         if use_splash:
656             icon = 'FILE_NEW'
657             show_more = len(paths) > (splash_limit - 1)
658             if show_more:
659                 paths = paths[:splash_limit - 2]
660         elif use_more:
661             icon = 'FILE_NEW'
662             paths = paths[splash_limit - 2:]
663             show_more = False
664         else:
665             icon = 'NONE'
666             show_more = False
667
668         # Draw application templates.
669         if not use_more:
670             props = layout.operator("wm.read_homefile", text="General", icon=icon)
671             props.app_template = ""
672
673         for d in paths:
674             props = layout.operator(
675                 "wm.read_homefile",
676                 text=bpy.path.display_name(d),
677                 icon=icon,
678             )
679             props.app_template = d
680
681         if show_more:
682             layout.menu("TOPBAR_MT_templates_more", text="...")
683
684     def draw(self, context):
685         TOPBAR_MT_file_new.draw_ex(self.layout, context)
686
687
688 class TOPBAR_MT_templates_more(Menu):
689     bl_label = "Templates"
690
691     def draw(self, context):
692         bpy.types.TOPBAR_MT_file_new.draw_ex(self.layout, context, use_more=True)
693
694
695 class TOPBAR_MT_file_import(Menu):
696     bl_idname = "TOPBAR_MT_file_import"
697     bl_label = "Import"
698
699     def draw(self, context):
700         if bpy.app.build_options.collada:
701             self.layout.operator("wm.collada_import", text="Collada (Default) (.dae)")
702         if bpy.app.build_options.alembic:
703             self.layout.operator("wm.alembic_import", text="Alembic (.abc)")
704
705
706 class TOPBAR_MT_file_export(Menu):
707     bl_idname = "TOPBAR_MT_file_export"
708     bl_label = "Export"
709
710     def draw(self, context):
711         if bpy.app.build_options.collada:
712             self.layout.operator("wm.collada_export", text="Collada (Default) (.dae)")
713         if bpy.app.build_options.alembic:
714             self.layout.operator("wm.alembic_export", text="Alembic (.abc)")
715
716
717 class TOPBAR_MT_file_external_data(Menu):
718     bl_label = "External Data"
719
720     def draw(self, context):
721         layout = self.layout
722
723         icon = 'CHECKBOX_HLT' if bpy.data.use_autopack else 'CHECKBOX_DEHLT'
724         layout.operator("file.autopack_toggle", icon=icon)
725
726         layout.separator()
727
728         pack_all = layout.row()
729         pack_all.operator("file.pack_all")
730         pack_all.active = not bpy.data.use_autopack
731
732         unpack_all = layout.row()
733         unpack_all.operator("file.unpack_all")
734         unpack_all.active = not bpy.data.use_autopack
735
736         layout.separator()
737
738         layout.operator("file.make_paths_relative")
739         layout.operator("file.make_paths_absolute")
740         layout.operator("file.report_missing_files")
741         layout.operator("file.find_missing_files")
742
743
744 class TOPBAR_MT_file_previews(Menu):
745     bl_label = "Data Previews"
746
747     def draw(self, context):
748         layout = self.layout
749
750         layout.operator("wm.previews_ensure")
751         layout.operator("wm.previews_batch_generate")
752
753         layout.separator()
754
755         layout.operator("wm.previews_clear")
756         layout.operator("wm.previews_batch_clear")
757
758
759 class TOPBAR_MT_render(Menu):
760     bl_label = "Render"
761
762     def draw(self, context):
763         layout = self.layout
764
765         rd = context.scene.render
766
767         layout.operator("render.render", text="Render Image", icon='RENDER_STILL').use_viewport = True
768         props = layout.operator("render.render", text="Render Animation", icon='RENDER_ANIMATION')
769         props.animation = True
770         props.use_viewport = True
771
772         layout.separator()
773
774         layout.operator("sound.mixdown", text="Render Audio...")
775
776         layout.separator()
777
778         layout.operator("render.view_show", text="View Render")
779         layout.operator("render.play_rendered_anim", text="View Animation")
780         layout.prop_menu_enum(rd, "display_mode", text="Display Mode")
781
782         layout.separator()
783
784         layout.prop(rd, "use_lock_interface", text="Lock Interface")
785
786
787 class TOPBAR_MT_edit(Menu):
788     bl_label = "Edit"
789
790     def draw(self, context):
791         layout = self.layout
792
793         layout.operator("ed.undo")
794         layout.operator("ed.redo")
795
796         layout.separator()
797
798         layout.operator("ed.undo_history", text="Undo History...")
799
800         layout.separator()
801
802         layout.operator("screen.repeat_last")
803         layout.operator("screen.repeat_history", text="Repeat History...")
804
805         layout.separator()
806
807         layout.operator("screen.redo_last", text="Adjust Last Operation...")
808
809         layout.separator()
810
811         layout.operator("wm.search_menu", text="Operator Search...", icon='VIEWZOOM')
812
813         layout.separator()
814
815         # Should move elsewhere (impacts outliner & 3D view).
816         tool_settings = context.tool_settings
817         layout.prop(tool_settings, "lock_object_mode")
818
819         layout.separator()
820
821         layout.operator("screen.userpref_show", text="Preferences...", icon='PREFERENCES')
822
823
824 class TOPBAR_MT_window(Menu):
825     bl_label = "Window"
826
827     def draw(self, context):
828         import sys
829
830         layout = self.layout
831
832         layout.operator("wm.window_new")
833         layout.operator("wm.window_new_main")
834
835         layout.separator()
836
837         layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER')
838
839         layout.separator()
840
841         layout.operator("screen.workspace_cycle", text="Next Workspace").direction = 'NEXT'
842         layout.operator("screen.workspace_cycle", text="Previous Workspace").direction = 'PREV'
843
844         layout.separator()
845
846         layout.prop(context.screen, "show_topbar")
847         layout.prop(context.screen, "show_statusbar")
848
849         layout.separator()
850
851         layout.operator("screen.screenshot")
852
853         if sys.platform[:3] == "win":
854             layout.separator()
855             layout.operator("wm.console_toggle", icon='CONSOLE')
856
857         if context.scene.render.use_multiview:
858             layout.separator()
859             layout.operator("wm.set_stereo_3d")
860
861
862 class TOPBAR_MT_help(Menu):
863     bl_label = "Help"
864
865     def draw(self, context):
866         # If 'url_prefill_from_blender' becomes slow it could be made into a separate operator
867         # to avoid constructing the bug report just to show this menu.
868         from bl_ui_utils.bug_report_url import url_prefill_from_blender
869
870         layout = self.layout
871
872         show_developer = context.preferences.view.show_developer_ui
873
874         layout.operator(
875             "wm.url_open", text="Manual", icon='HELP',
876         ).url = "https://docs.blender.org/manual/en/dev/"
877
878         layout.operator(
879             "wm.url_open", text="Report a Bug", icon='URL',
880         ).url = url_prefill_from_blender()
881
882         layout.separator()
883
884         layout.operator(
885             "wm.url_open", text="User Communities", icon='URL',
886         ).url = "https://www.blender.org/community/"
887         layout.operator(
888             "wm.url_open", text="Developer Community", icon='URL',
889         ).url = "https://www.blender.org/get-involved/developers/"
890
891         layout.separator()
892
893         layout.operator(
894             "wm.url_open", text="Blender Website", icon='URL',
895         ).url = "https://www.blender.org"
896         layout.operator(
897             "wm.url_open", text="Release Notes", icon='URL',
898         ).url = "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
899         layout.operator(
900             "wm.url_open", text="Credits", icon='URL',
901         ).url = "https://www.blender.org/about/credits/"
902
903         layout.separator()
904
905         layout.operator(
906             "wm.url_open", text="Blender Store", icon='URL',
907         ).url = "https://store.blender.org"
908         layout.operator(
909             "wm.url_open", text="Development Fund", icon='URL'
910         ).url = "https://fund.blender.org"
911         layout.operator(
912             "wm.url_open", text="Donate", icon='URL',
913         ).url = "https://www.blender.org/foundation/donation-payment/"
914
915         layout.separator()
916
917         if show_developer:
918             layout.operator(
919                 "wm.url_open", text="Python API Reference", icon='URL',
920             ).url = bpy.types.WM_OT_doc_view._prefix
921
922             layout.operator("wm.operator_cheat_sheet", icon='TEXT')
923
924         layout.operator("wm.sysinfo")
925
926         layout.separator()
927
928         layout.operator("wm.splash", icon='BLENDER')
929
930
931 class TOPBAR_MT_file_context_menu(Menu):
932     bl_label = "File Context Menu"
933
934     def draw(self, context):
935         layout = self.layout
936
937         layout.operator_context = 'INVOKE_AREA'
938         layout.operator("wm.read_homefile", text="New", icon='FILE_NEW')
939         layout.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
940
941         layout.separator()
942
943         layout.operator("wm.link", text="Link...", icon='LINK_BLEND')
944         layout.operator("wm.append", text="Append...", icon='APPEND_BLEND')
945
946         layout.separator()
947
948         layout.menu("TOPBAR_MT_file_import", icon='IMPORT')
949         layout.menu("TOPBAR_MT_file_export", icon='EXPORT')
950
951
952 class TOPBAR_MT_window_context_menu(Menu):
953     bl_label = "Window Context Menu"
954
955     def draw(self, context):
956         layout = self.layout
957
958         layout.operator_context = 'EXEC_AREA'
959
960         layout.operator("wm.window_new")
961         layout.operator("wm.window_new_main")
962
963         layout.operator_context = 'INVOKE_AREA'
964
965         layout.operator("screen.area_dupli", icon='DUPLICATE')
966
967         layout.separator()
968
969         layout.operator("screen.area_split", text="Horizontal Split").direction = 'HORIZONTAL'
970         layout.operator("screen.area_split", text="Vertical Split").direction = 'VERTICAL'
971
972         layout.separator()
973
974         layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER')
975
976         layout.separator()
977
978         layout.operator("screen.userpref_show", text="Preferences...", icon='PREFERENCES')
979
980
981 class TOPBAR_MT_workspace_menu(Menu):
982     bl_label = "Workspace"
983
984     def draw(self, context):
985         layout = self.layout
986
987         layout.operator("workspace.duplicate", text="Duplicate", icon='DUPLICATE')
988         if len(bpy.data.workspaces) > 1:
989             layout.operator("workspace.delete", text="Delete", icon='REMOVE')
990
991         layout.separator()
992
993         layout.operator("workspace.reorder_to_front", text="Reorder to Front", icon='TRIA_LEFT_BAR')
994         layout.operator("workspace.reorder_to_back", text="Reorder to Back", icon='TRIA_RIGHT_BAR')
995
996         layout.separator()
997
998         # For key binding discoverability.
999         props = layout.operator("screen.workspace_cycle", text="Previous Workspace")
1000         props.direction = 'PREV'
1001         props = layout.operator("screen.workspace_cycle", text="Next Workspace")
1002         props.direction = 'NEXT'
1003
1004
1005 class TOPBAR_PT_active_tool(Panel):
1006     bl_space_type = 'PROPERTIES'
1007     bl_region_type = 'WINDOW'
1008     bl_category = ""
1009     bl_context = ".active_tool"  # dot on purpose (access from tool settings)
1010     bl_label = "Active Tool"
1011     bl_options = {'HIDE_HEADER'}
1012
1013     def draw(self, context):
1014         layout = self.layout
1015
1016         # Panel display of topbar tool settings.
1017         # currently displays in tool settings, keep here since the same functionality is used for the topbar.
1018
1019         layout.use_property_split = True
1020         layout.use_property_decorate = False
1021
1022         from .space_toolsystem_common import ToolSelectPanelHelper
1023         ToolSelectPanelHelper.draw_active_tool_header(context, layout, show_tool_name=True)
1024
1025
1026 # Grease Pencil Object - Primitive curve
1027 class TOPBAR_PT_gpencil_primitive(Panel):
1028     bl_space_type = 'VIEW_3D'
1029     bl_region_type = 'HEADER'
1030     bl_label = "Primitives"
1031
1032     def draw(self, context):
1033         settings = context.tool_settings.gpencil_sculpt
1034
1035         layout = self.layout
1036         # Curve
1037         layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
1038
1039
1040 classes = (
1041     TOPBAR_HT_upper_bar,
1042     TOPBAR_HT_lower_bar,
1043     TOPBAR_MT_file_context_menu,
1044     TOPBAR_MT_window_context_menu,
1045     TOPBAR_MT_workspace_menu,
1046     TOPBAR_MT_editor_menus,
1047     TOPBAR_MT_file,
1048     TOPBAR_MT_file_new,
1049     TOPBAR_MT_templates_more,
1050     TOPBAR_MT_file_import,
1051     TOPBAR_MT_file_export,
1052     TOPBAR_MT_file_external_data,
1053     TOPBAR_MT_file_previews,
1054     TOPBAR_MT_edit,
1055     TOPBAR_MT_render,
1056     TOPBAR_MT_window,
1057     TOPBAR_MT_help,
1058     TOPBAR_PT_active_tool,
1059     TOPBAR_PT_gpencil_layers,
1060     TOPBAR_PT_gpencil_primitive,
1061 )
1062
1063 if __name__ == "__main__":  # only for live edit.
1064     from bpy.utils import register_class
1065     for cls in classes:
1066         register_class(cls)