Update preference using compact, add compact mode switch in menu

This commit is contained in:
Francesco Bellini 2024-09-08 18:27:02 +02:00
parent a61b819400
commit cddbef9f62
10 changed files with 168 additions and 153 deletions

View File

@ -14,20 +14,20 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import bpy import bpy
from .classes.funcs import select_all, draw_workspace_menu from .sw.funcs import select_all, workspace_menu
from .classes.ops import SW_OT_workspace, SW_OT_link_workspace, SW_OT_move, SW_OT_link_all, SW_OT_remove, SW_OT_switch, SW_OT_rename, SW_OT_copy_from_scene from .sw.ops import SW_OT_workspace, SW_OT_link_workspace, SW_OT_move, SW_OT_link_all, SW_OT_remove, SW_OT_switch, SW_OT_rename, SW_OT_copy_from_scene
from .classes.topbar import TOPBAR_HT_upper_bar from .sw.topbar import TOPBAR_HT_upper_bar
from .classes.panel import SW_PT_select_scene_workspaces from .sw.panel import SW_PT_select_scene_workspaces
from .classes.menu import SW_MT_missing_workspaces, SW_MT_copy_from from .sw.menu import SW_MT_missing_workspaces, SW_MT_copy_from
from .classes.prefs import SWPreferences from .sw.prefs import SW_Preferences
bl_info = { bl_info = {
"id": "scene_workspaces", "id": "scene_workspaces",
"name": "Scene Workspaces", "name": "Scene Workspaces",
"tagline": "Filter and rearrange workspaces individually for each scene", "tagline": "Filter and sort your workspaces for each scene independently",
"blender": (4, 2, 0), "blender": (4, 2, 0),
"location": "Workspaces topbar, Properties > Scene", "location": "Workspaces topbar, Properties > Scene",
"category": ["System", "User Interface", "Scene"], "category": ["Scene", "System", "User Interface"],
"support": "COMMUNITY", "support": "COMMUNITY",
"blender_manifest": "blender_manifest.toml" "blender_manifest": "blender_manifest.toml"
} }
@ -37,14 +37,14 @@ classes = [
SW_OT_switch, SW_OT_switch,
SW_OT_move, SW_OT_move,
SW_OT_remove, SW_OT_remove,
SW_OT_link_all,
SW_OT_link_workspace,
SW_OT_rename, SW_OT_rename,
SW_OT_link_workspace,
SW_OT_link_all,
SW_OT_copy_from_scene, SW_OT_copy_from_scene,
SW_MT_missing_workspaces,
SW_MT_copy_from, SW_MT_copy_from,
SW_MT_missing_workspaces,
SW_PT_select_scene_workspaces, SW_PT_select_scene_workspaces,
SWPreferences, SW_Preferences,
TOPBAR_HT_upper_bar TOPBAR_HT_upper_bar
] ]
@ -55,13 +55,13 @@ def register():
og_header = bpy.types.TOPBAR_HT_upper_bar; og_header = bpy.types.TOPBAR_HT_upper_bar;
for c in classes: for c in classes:
bpy.utils.register_class(c) bpy.utils.register_class(c)
bpy.types.TOPBAR_MT_workspace_menu.prepend(draw_workspace_menu) bpy.types.TOPBAR_MT_workspace_menu.prepend(workspace_menu)
def unregister(): def unregister():
for c in reversed(classes): for c in reversed(classes):
bpy.utils.unregister_class(c) bpy.utils.unregister_class(c)
bpy.utils.register_class(og_header) bpy.utils.register_class(og_header)
bpy.types.TOPBAR_MT_workspace_menu.remove(draw_workspace_menu) bpy.types.TOPBAR_MT_workspace_menu.remove(workspace_menu)
if __name__ == "__main__": if __name__ == "__main__":
register() register()

View File

@ -2,7 +2,7 @@ schema_version = "1.0.0"
id = "scene_workspaces" id = "scene_workspaces"
version = "1.0.0" version = "1.0.0"
name = "Scene Workspaces" name = "Scene Workspaces"
tagline = "Filter and rearrange workspaces individually for each scene" tagline = "Filter and sort your workspaces for each scene independently"
maintainer = "Francesco Bellini <doc.open.dev@gmail.com>" maintainer = "Francesco Bellini <doc.open.dev@gmail.com>"
type = "add-on" type = "add-on"
website = "https://projects.blender.org/Francesco-Bellini/scene_workspaces_addon" website = "https://projects.blender.org/Francesco-Bellini/scene_workspaces_addon"

View File

@ -1,74 +0,0 @@
import bpy
from .. import __package__ as base_package
def has_data():
return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name]
def get_scene_workspaces(scene = None):
if has_data():
return bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces']
else:
return []
def prefs():
return bpy.context.preferences.addons[base_package].preferences
def get_use_global():
return prefs().use_global
def get_show_switch():
return prefs().show_switch
def get_active_spacing():
return prefs().active_spacing
def get_quick_unlink():
return prefs().quick_unlink
def set_scene_workspaces(workspaces):
bpy.data.scenes[bpy.context.scene.name]['scene_workspaces'] = workspaces
def select_all():
workspaces = []
for w in bpy.data.workspaces:
workspaces.append(w.name)
set_scene_workspaces(workspaces)
def get_other_scene_names():
return [s.name for s in bpy.data.scenes if s.name != bpy.context.scene.name]
def draw_workspace_ops(layout, i, text=False, name=None):
layout.separator()
remove = layout.operator(
"sw.remove", text="Unlink workspace from scene" if text else "", icon='X')
remove.index = i
layout.separator()
rename = layout.operator(
"sw.rename", text="Rename" if text else "", icon="TEXT")
rename.current_name = name if name else bpy.context.window.workspace.name
rename.new_name = rename.current_name
layout.separator()
top = layout.operator("sw.move", text="Reorder to Front" if text else "",
icon='TRIA_LEFT_BAR' if text else 'TRIA_UP_BAR')
top.index = i
top.top = True
up = layout.operator("sw.move", text="Move Left" if text else "",
icon='TRIA_LEFT' if text else 'TRIA_UP')
up.index = i
up.up = 1
down = layout.operator("sw.move", text="Move Right" if text else "",
icon='TRIA_RIGHT' if text else 'TRIA_DOWN')
down.index = i
down.up = -1
bottom = layout.operator("sw.move", text="Reorder to Back" if text else "",
icon='TRIA_RIGHT_BAR' if text else 'TRIA_DOWN_BAR')
bottom.index = i
bottom.top = False
def draw_workspace_menu(self, context):
layout = self.layout
use_global = get_use_global()
if not use_global and has_data():
draw_workspace_ops(layout, get_scene_workspaces().index(
context.window.workspace.name), True)
layout.separator()

View File

@ -1,23 +0,0 @@
import bpy
from bpy.types import AddonPreferences
from bpy.props import BoolProperty
from .. import __package__ as base_package
class SWPreferences(AddonPreferences):
bl_idname = base_package
use_global: BoolProperty(name="Use default workspaces", description="Switch the workspace topbar between Scene Workspaces and the default one", default=False)
show_switch: BoolProperty(name="Show Switch", description="Show/Hide the switch button next to the workspaces topbar. Useful for quickly lookup the original workspaces topbar", default=True)
active_spacing: BoolProperty(name="Active workspace spacing", description="Add space around the active workspace", default=True)
quick_unlink: BoolProperty(name="Show Quick Unlink", description="Show/Hide the unlink button at left of the active workspace", default=True)
def draw(self, context):
layout = self.layout
layout.prop(self, "use_global")
layout.prop(self, "show_switch")
box = layout.box()
box.label(text="UI Options")
box.prop(self, "quick_unlink")
box.prop(self, "active_spacing")

80
addon/sw/funcs.py Normal file
View File

@ -0,0 +1,80 @@
from .. import __package__ as base_package
import bpy
def has_data():
return 'scene_workspaces' in bpy.data.scenes[bpy.context.scene.name]
def get_scene_workspaces(scene = None):
if has_data():
return bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces']
else:
return []
def prefs():
return bpy.context.preferences.addons[base_package].preferences
def get_use_topbar():
return prefs().use_topbar
def get_switch():
return prefs().switch
def get_compact():
return prefs().compact
def set_scene_workspaces(workspaces, scene = None):
bpy.data.scenes[scene if scene else bpy.context.scene.name]['scene_workspaces'] = workspaces
def select_all():
workspaces = []
for w in bpy.data.workspaces:
workspaces.append(w.name)
set_scene_workspaces(workspaces)
def get_other_scene_names():
return [s.name for s in bpy.data.scenes if s.name != bpy.context.scene.name]
def workspace_ops(layout, i, menu_mode=False, name=None):
layout.separator()
if menu_mode:
layout.prop(prefs(), "compact", text="Compact Mode")
layout.separator()
def remove(l):
remove = l.operator(
"sw.remove", text="Unlink workspace from scene" if menu_mode else "", icon='X')
remove.index = i
if menu_mode:
remove(layout)
layout.separator()
rename = layout.operator(
"sw.rename", text="Rename" if menu_mode else "", icon="TEXT")
rename.current_name = name if name else bpy.context.window.workspace.name
rename.new_name = rename.current_name
layout.separator()
top = layout.operator("sw.move", text="Reorder to Front" if menu_mode else "",
icon='TRIA_LEFT_BAR' if menu_mode else 'TRIA_UP_BAR')
top.index = i
top.top = True
up = layout.operator("sw.move", text="Move Left" if menu_mode else "",
icon='TRIA_LEFT' if menu_mode else 'TRIA_UP')
up.index = i
up.up = 1
down = layout.operator("sw.move", text="Move Right" if menu_mode else "",
icon='TRIA_RIGHT' if menu_mode else 'TRIA_DOWN')
down.index = i
down.up = -1
bottom = layout.operator("sw.move", text="Reorder to Back" if menu_mode else "",
icon='TRIA_RIGHT_BAR' if menu_mode else 'TRIA_DOWN_BAR')
bottom.index = i
bottom.top = False
if not menu_mode:
layout.separator()
remove(layout)
def workspace_menu(self, context):
layout = self.layout
use_topbar = get_use_topbar()
if use_topbar and has_data():
workspace_ops(layout, get_scene_workspaces().index(
context.window.workspace.name), True)
layout.separator()

View File

@ -22,4 +22,4 @@ class SW_MT_missing_workspaces(Menu):
layout.separator() layout.separator()
for w in bpy.data.workspaces: for w in bpy.data.workspaces:
if w.name not in get_scene_workspaces(): if w.name not in get_scene_workspaces():
layout.operator("sw.link_workspace", text=w.name, icon="ADD").workspace = w.name layout.operator("sw.link_workspace", text=w.name, icon="LINKED").workspace = w.name

View File

@ -1,9 +1,8 @@
from .. import __package__ as base_package from .. import __package__ as base_package
import time
import bpy import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty, IntProperty from bpy.props import StringProperty, BoolProperty, IntProperty
from .funcs import select_all, get_scene_workspaces, set_scene_workspaces, get_use_global, has_data, prefs from .funcs import select_all, get_scene_workspaces, set_scene_workspaces, get_use_topbar, has_data, prefs
class SW_OT_switch(Operator): class SW_OT_switch(Operator):
bl_idname = "sw.switch" bl_idname = "sw.switch"
@ -11,7 +10,7 @@ class SW_OT_switch(Operator):
bl_description = "Switch workspaces topbar between Scene Workspaces and default" bl_description = "Switch workspaces topbar between Scene Workspaces and default"
def execute(self, context): def execute(self, context):
prefs().use_global = not get_use_global() prefs().use_topbar = not get_use_topbar()
context.preferences.is_dirty = True context.preferences.is_dirty = True
return {'FINISHED'} return {'FINISHED'}
@ -65,7 +64,7 @@ class SW_OT_move(Operator):
class SW_OT_rename(Operator): class SW_OT_rename(Operator):
bl_idname = "sw.rename" bl_idname = "sw.rename"
bl_label = "Rename workspace" bl_label = "Rename workspace"
bl_description = "Rename the current workspace (this rename both the global workspace and the Scene workspace)" bl_description = "Rename the current workspace (this rename both the global workspace as the scene workspace)"
current_name: StringProperty(name="Current Name", options={'HIDDEN'}) current_name: StringProperty(name="Current Name", options={'HIDDEN'})
new_name: StringProperty( new_name: StringProperty(
@ -74,12 +73,18 @@ class SW_OT_rename(Operator):
def execute(self, context): def execute(self, context):
w = bpy.data.workspaces[self.current_name] w = bpy.data.workspaces[self.current_name]
# Update global workspace name
w.name = self.new_name w.name = self.new_name
l = get_scene_workspaces() # Update name in all scenes, if present, to avoid missing workspace
i = l.index(self.current_name) for s in bpy.data.scenes:
l.pop(i) l = get_scene_workspaces(s.name)
l.insert(i, self.new_name) try:
set_scene_workspaces(l) i = l.index(self.current_name)
l.pop(i)
l.insert(i, self.new_name)
set_scene_workspaces(l, s.name)
finally:
continue
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):

View File

@ -1,6 +1,6 @@
import bpy import bpy
from bpy.types import Panel from bpy.types import Panel
from .funcs import get_scene_workspaces, draw_workspace_ops from .funcs import get_scene_workspaces, workspace_ops
class SW_PT_select_scene_workspaces(Panel): class SW_PT_select_scene_workspaces(Panel):
bl_idname = "sw.select_scene_workspaces" bl_idname = "sw.select_scene_workspaces"
@ -12,10 +12,10 @@ class SW_PT_select_scene_workspaces(Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.label(text="Workspaces linked to this Scene:")
sw = get_scene_workspaces()
layout.label(text="Workspaces linked to this Scene:")
box = layout.box() box = layout.box()
sw = get_scene_workspaces()
if not sw: if not sw:
box.label(text="There are no workspaces linked right now...") box.label(text="There are no workspaces linked right now...")
box.operator("sw.link_all") box.operator("sw.link_all")
@ -27,17 +27,17 @@ class SW_PT_select_scene_workspaces(Panel):
row = box2.row(align=True) row = box2.row(align=True)
row.scale_y = 2.1 row.scale_y = 2.1
row.label(text=f"{w}") row.label(text=f"{w}")
draw_workspace_ops(row, i, False, w) workspace_ops(row, i, False, w)
i += 1 i += 1
box.separator() box.separator()
layout.separator() layout.separator()
layout.label(text="Link other workspaces")
l = [x for x in bpy.data.workspaces if x.name not in sw]
layout.label(text="Link other workspaces")
box2 = layout.box() box2 = layout.box()
row = box2.row() row = box2.row()
row_count = 0 row_count = 0
l = [x for x in bpy.data.workspaces if x.name not in sw]
for w in l: for w in l:
if row_count > 0 and row_count % 4 == 0: if row_count > 0 and row_count % 4 == 0:
row = box2.row() row = box2.row()

20
addon/sw/prefs.py Normal file
View File

@ -0,0 +1,20 @@
from .. import __package__ as base_package
import bpy
from bpy.types import AddonPreferences
from bpy.props import BoolProperty
class SW_Preferences(AddonPreferences):
bl_idname = base_package
use_topbar: BoolProperty(name="Use Scene Workspaces topbar", description="Switch the workspace topbar between Scene Workspaces and the default one", default=True)
switch: BoolProperty(name="Show Switch", description="Show/Hide the switch button next to the workspaces topbar. Useful for quickly lookup the original workspaces topbar", default=True)
compact: BoolProperty(name="Use Compact Mode (for active workspace)", description="If disabled, adds spacing around the active workspace and a quick unlink button", default=True)
def draw(self, context):
layout = self.layout
layout.prop(self, "use_topbar")
layout.prop(self, "switch")
box = layout.box()
box.label(text="UI Options")
box.prop(self, "compact")

View File

@ -1,7 +1,7 @@
from .. import __package__ as base_package from .. import __package__ as base_package
import bpy import bpy
from bpy.types import Header, Panel from bpy.types import Header, Panel
from .funcs import get_scene_workspaces, get_use_global, get_show_switch, get_quick_unlink, get_active_spacing from .funcs import get_scene_workspaces, get_use_topbar, get_switch, get_compact
# Ref TOPBAR_HT_upper_bar https://projects.blender.org/blender/blender/src/commit/2204157a2c9fc926643b0e39968602c750d9b5e6/scripts/startup/bl_ui/space_topbar.py#L14 # Ref TOPBAR_HT_upper_bar https://projects.blender.org/blender/blender/src/commit/2204157a2c9fc926643b0e39968602c750d9b5e6/scripts/startup/bl_ui/space_topbar.py#L14
class TOPBAR_HT_upper_bar(Header): class TOPBAR_HT_upper_bar(Header):
@ -16,7 +16,6 @@ class TOPBAR_HT_upper_bar(Header):
self.draw_left(context) self.draw_left(context)
def draw_left(self, context): def draw_left(self, context):
global workspace_order
layout = self.layout layout = self.layout
window = context.window window = context.window
@ -26,46 +25,45 @@ class TOPBAR_HT_upper_bar(Header):
layout.separator() layout.separator()
# Edit start
# Edited use_topbar = get_use_topbar()
use_global = get_use_global() compact = get_compact()
quick_unlink = get_quick_unlink() switch= get_switch()
active_spacing = get_active_spacing()
if not screen.show_fullscreen: if not screen.show_fullscreen:
main_col = layout.column() main_col = layout.column()
spacer_row = main_col.row() spacer_row = main_col.row()
spacer_row.scale_y = .33 spacer_row.scale_y = 1
main_row = main_col.row() main_row = main_col.row()
sy = 1.18 sy = 1.18
sx = 1.12 sx = 1.18
if get_show_switch(): if switch:
col = main_row.column() col = main_row.column()
# Show/Hide Custom Topbar # Show/Hide Custom Topbar
col.operator("sw.switch", text="", emboss=False, icon="CHECKBOX_DEHLT" if use_global else "CHECKBOX_HLT") col.operator("sw.switch", text="", emboss=False, icon="CHECKBOX_HLT" if use_topbar else "CHECKBOX_DEHLT")
if use_global: if not use_topbar:
# Original # Original
layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu") layout.template_ID_tabs(window, "workspace", new="workspace.add", menu="TOPBAR_MT_workspace_menu")
else: else:
r = main_row.row(align=True) r = main_row.row(align=True)
r.scale_x = 0.87 r.scale_x = 0.87
r.scale_y = sy r.scale_y = sy
ws = get_scene_workspaces() sw = get_scene_workspaces()
# Link all (with no linked workspaces) # Link all (with no linked workspaces)
if not ws and bpy.data.workspaces: if not sw and bpy.data.workspaces:
c = r.column() c = r.column()
c.scale_x = 1.4 c.scale_x = 1.4
c.operator("sw.link_all", text="Link all workspaces", icon="LINKED") c.operator("sw.link_all", text="Link all workspaces", icon="LINKED")
active_not_here = True active_not_here = True
for w in ws: for w in sw:
i = ws.index(w) i = sw.index(w)
active = bpy.context.window.workspace.name == w active = bpy.context.window.workspace.name == w
if active and i > 0 and active_spacing: if active and i > 0 and not compact:
r.separator() r.separator()
ab = r.box() ab = r.box()
ab = ab.row(align=True) ab = ab.row(align=True)
# Workspace unlink # Workspace unlink
if active and quick_unlink: if active and not compact:
col = ab.column() col = ab.column()
col.scale_x = sx col.scale_x = sx
col.operator("sw.remove", text="", icon="X", emboss=False).index = i col.operator("sw.remove", text="", icon="X", emboss=False).index = i
@ -78,23 +76,32 @@ class TOPBAR_HT_upper_bar(Header):
col.scale_x = sx col.scale_x = sx
col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS")
active_not_here = False active_not_here = False
if active_spacing: if not compact:
r.separator() r.separator()
if not compact:
r.separator()
size = sx if compact else sx * 1.25
# Link other workspaces # Link other workspaces
if [x for x in bpy.data.workspaces if x.name not in get_scene_workspaces()]: if len(bpy.data.workspaces) > len(sw):
c = r.column() c = r.column()
c.scale_x = sx c.scale_x = size
c.menu("SW_MT_missing_workspaces", text="", icon="WORKSPACE") c.menu("SW_MT_missing_workspaces", text="", icon="WORKSPACE")
if not compact:
r.separator()
# Duplicate linked workspaces from other scenes # Duplicate linked workspaces from other scenes
if len(bpy.data.scenes) > 1: if len(bpy.data.scenes) > 1:
c = r.column() c = r.column()
c.scale_x = sx c.scale_x = size
c.menu("SW_MT_copy_from", text="", icon="DUPLICATE") c.menu("SW_MT_copy_from", text="", icon="DUPLICATE")
if not compact:
r.separator()
# Original Add Workspace menu # Original Add Workspace menu
c = r.column() c = r.column()
c.scale_x = sx c.scale_x = size
c.operator("workspace.add", text="", icon="ADD") c.operator("workspace.add", text="", icon="ADD")
if not compact:
r.separator()
# Active (but not linked) workspace # Active (but not linked) workspace
if active_not_here: if active_not_here:
@ -108,7 +115,7 @@ class TOPBAR_HT_upper_bar(Header):
col = r.box() col = r.box()
col.scale_x = sx col.scale_x = sx
col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS") col.menu("TOPBAR_MT_workspace_menu", text="", icon="OPTIONS")
# End Edited # Edit - End
else: else:
layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous") layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")