diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 0ae3b24578b..f491deb350e 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -417,7 +417,8 @@ MODULE_GROUPING = { BLENDER_REVISION = str(bpy.app.build_hash, 'utf_8') # '2.83.0 Beta' or '2.83.0' or '2.83.1' -BLENDER_VERSION_DOTS = bpy.app.version_string +BLENDER_VERSION_STRING = bpy.app.version_string +BLENDER_VERSION_DOTS = "%d.%d" % (bpy.app.version[0], bpy.app.version[1]) if BLENDER_REVISION != "Unknown": # SHA1 Git hash @@ -1724,11 +1725,11 @@ def write_sphinx_conf_py(basepath): fw("import sys, os\n\n") fw("extensions = ['sphinx.ext.intersphinx']\n\n") fw("intersphinx_mapping = {'blender_manual': ('https://docs.blender.org/manual/en/dev/', None)}\n\n") - fw("project = 'Blender %s Python API'\n" % BLENDER_VERSION_DOTS) + fw("project = 'Blender %s Python API'\n" % BLENDER_VERSION_STRING) fw("master_doc = 'index'\n") fw("copyright = u'Blender Foundation'\n") - fw("version = '%s'\n" % BLENDER_VERSION_HASH) - fw("release = '%s'\n" % BLENDER_VERSION_HASH) + fw("version = '%s'\n" % BLENDER_VERSION_DOTS) + fw("release = '%s'\n" % BLENDER_VERSION_DOTS) # Quiet file not in table-of-contents warnings. fw("exclude_patterns = [\n") @@ -1749,6 +1750,7 @@ except ModuleNotFoundError: fw("if html_theme == 'sphinx_rtd_theme':\n") fw(" html_theme_options = {\n") + fw(" 'display_version': False\n") # fw(" 'analytics_id': '',\n") # fw(" 'collapse_navigation': True,\n") fw(" 'sticky_navigation': False,\n") @@ -1765,10 +1767,15 @@ except ModuleNotFoundError: fw("html_show_search_summary = True\n") fw("html_split_index = True\n") fw("html_static_path = ['static']\n") + fw("templates_path = ['templates']\n") + fw("html_context = {'commit': '%s'}\n" % BLENDER_VERSION_HASH) fw("html_extra_path = ['static/favicon.ico', 'static/blender_logo.svg']\n") fw("html_favicon = 'static/favicon.ico'\n") fw("html_logo = 'static/blender_logo.svg'\n") fw("html_last_updated_fmt = '%m/%d/%Y'\n\n") + fw("if html_theme == 'sphinx_rtd_theme':\n") + fw(" html_css_files = ['css/version_switch.css']\n") + fw(" html_js_files = ['js/version_switch.js']\n") # needed for latex, pdf gen fw("latex_elements = {\n") @@ -2125,6 +2132,9 @@ def copy_theme_assets(basepath): shutil.copytree(os.path.join(SCRIPT_DIR, "static"), os.path.join(basepath, "static"), copy_function=shutil.copy) + shutil.copytree(os.path.join(SCRIPT_DIR, "templates"), + os.path.join(basepath, "templates"), + copy_function=shutil.copy) def rna2sphinx(basepath): diff --git a/doc/python_api/static/css/version_switch.css b/doc/python_api/static/css/version_switch.css new file mode 100644 index 00000000000..360ff2eea0e --- /dev/null +++ b/doc/python_api/static/css/version_switch.css @@ -0,0 +1,127 @@ +/* Override RTD theme */ +.rst-versions { + border-top: 0px; + overflow: visible; +} +.version-btn.vdeact { + cursor: default; + color: dimgray; +} + +.version-btn.vdeact::after { + content: ""; +} +#versionwrap { + display: flex; + padding-top: 2px; + font-size: 90%; + justify-content: center; + flex-wrap: wrap; +} +.version-btn { + display: inline-block; + background-color: #272525; + width: 140px; + text-align: center; + padding: 3px 10px; + margin: 0px 5px 4px; + vertical-align: middle; + color: #27AE60; + border: solid 1px #444444; + border-radius: 3px; + cursor: pointer; + z-index: 400; + transition: border-color 0.4s; +} +.version-btn::after { + content:"\f0d8"; + display: inline; + font: normal normal normal 16px/1 FontAwesome; + color: #8d8c8c; + vertical-align: top; + padding-left: 0.5em; +} +.version-btn-open::after { + color: gray; +} +.version-btn:hover, .version-btn:focus { + border-color: #525252; +} +.version-btn-open { + color: gray; + border: solid 1px gray; +} +.version-btn.wait { + cursor: wait; +} +.version-btn.disabled { + cursor: not-allowed; + color: dimgray; +} +.version-dialog { + display: none; + position: absolute; + bottom: 28px; + width: 140px; + margin: 0 5px; + padding-bottom: 4px; + background-color: #0003; + border-radius: 3px; + box-shadow: 0 0 6px #000C; + z-index: 999; + max-height: calc(100vh - 30px); + overflow-y: auto; + cursor: default; +} +.version-title { + padding: 5px; + color: black; + text-align: center; + font-size: 102%; + background-color: #27ae60; + border-bottom: solid 1.5px #444; +} +.version-list { + margin-bottom: 4px; + text-align: center; + background-color: #000C; + border: solid 1px gray; + border-radius: 0px 0px 3px 3px; +} +.version-list a, .version-list span, .version-list li { + position: relative; + display: block; + font-size: 98%; + line-height: 1.15; + width: 100%; + margin: 0; + padding: 4px 0px; + color: #404040; +} +.version-list li { + background-color: #ede9e9; + color: #404040; + padding: 1px; +} +.version-list li:hover, .version-list li a:focus { + background-color: #b9cfda; +} +.version-list li.selected, .version-list li.selected:hover { + background-color: #8d8c8c; +} +.version-list li.selected span { + cursor: default; + outline-color: red; +} +.version-arrow { + position: absolute; + width: 8px; + height: 8px; + left: 50%; + bottom: 4px; + margin-left: -4px; + transform: rotate(225deg); + background: #ede9e9; + border: 1px solid gray; + border-width: 1px 0 0 1px; +} diff --git a/doc/python_api/static/js/version_switch.js b/doc/python_api/static/js/version_switch.js new file mode 100644 index 00000000000..88468b163e4 --- /dev/null +++ b/doc/python_api/static/js/version_switch.js @@ -0,0 +1,323 @@ +(function() { // switch: v1.2 +"use strict"; + +var versionsFileUrl = "https://docs.blender.org/versions.json" + +var all_versions; + +var Popover = function() { + function Popover(id) + { + this.isOpen = false; + this.type = (id === "version-popover"); + this.$btn = $('#' + id); + this.$dialog = this.$btn.next(); + this.$list = this.$dialog.children("ul"); + this.sel = null; + this.beforeInit(); + } + + Popover.prototype = { + beforeInit : function() { + var that = this; + this.$btn.on("click", function(e) { + that.init(); + e.preventDefault(); + e.stopPropagation(); + }); + this.$btn.on("keydown", function(e) { + if (that.btnKeyFilter(e)) { + that.init(); + e.preventDefault(); + e.stopPropagation(); + } + }); + }, + init : function() { + this.$btn.off("click"); + this.$btn.off("keydown"); + + if (all_versions === undefined) { + this.$btn.addClass("wait"); + this.loadVL(this); + } + else { + this.afterLoad(); + } + }, + loadVL : function(that) { + $.getJSON(versionsFileUrl, function(data) { + all_versions = data; + that.afterLoad(); + return true; + }).fail(function() { + console.log("Version Switch Error: versions.json could not be loaded."); + that.$btn.addClass("disabled"); + return false; + }); + }, + afterLoad : function() { + var release = DOCUMENTATION_OPTIONS.VERSION; + const m = release.match(/\d\.\d+/g); + if (m) { + release = m[0]; + } + + this.warnOld(release, all_versions); + + var version = this.getNamed(release); + var list = this.buildList(version); + + this.$list.children(":first-child").remove(); + this.$list.append(list); + var that = this; + this.$list.on("keydown", function(e) { + that.keyMove(e); + }); + + this.$btn.removeClass("wait"); + this.btnOpenHandler(); + this.$btn.on("mousedown", function(e) { + that.btnOpenHandler(); + e.preventDefault() + }); + this.$btn.on("keydown", function(e) { + if (that.btnKeyFilter(e)) { + that.btnOpenHandler(); + } + }); + }, + warnOld : function(release, all_versions) { + // Note this is effectively disabled now, two issues must fixed: + // * versions.js does not contain a current entry, because that leads to + // duplicate version numbers in the menu. These need to be deduplicated. + // * It only shows the warning after opening the menu to switch version + // when versions.js is loaded. This is too late to be useful. + var current = all_versions.current + if (!current) + { + // console.log("Version Switch Error: no 'current' in version.json."); + return; + } + const m = current.match(/\d\.\d+/g); + if (m) { + current = parseFloat(m[0]); + } + if (release < current) { + var currentURL = window.location.pathname.replace(release, current); + var warning = $('
' + + '

Note

' + + '

' + + 'You are not using the most up to date version of the documentation. ' + + ' is the newest version.' + + '

' + + '
'); + + warning.find('a').attr('href', currentURL).text(current); + + var body = $("div.body"); + if (!body.length) { + body = $("div.document"); + } + body.prepend(warning); + } + }, + buildList : function(v) { + var url = new URL(window.location.href); + let pathSplit = [ "", "api", v ]; + if (url.pathname.startsWith("/api/")) { + pathSplit.push(url.pathname.split('/').slice(4).join('/')); + } + else { + pathSplit.push(url.pathname.substring(1)); + } + if (this.type) { + var dyn = all_versions; + var cur = v; + } + var buf = []; + var that = this; + $.each(dyn, function(ix, title) { + buf.push("' + + title + ''); + } + else { + pathSplit[2 + that.type] = ix; + var href = new URL(url); + href.pathname = pathSplit.join('/'); + buf.push(' tabindex="-1" role="presentation">' + + title + ''); + } + }); + return buf.join(''); + }, + getNamed : function(v) { + $.each(all_versions, function(ix, title) { + if (ix === "master" || ix === "latest") { + var m = title.match(/\d\.\d[\w\d\.]*/)[0]; + if (parseFloat(m) == v) { + v = ix; + return false; + } + } + }); + return v; + }, + dialogToggle : function(speed) { + var wasClose = !this.isOpen; + var that = this; + if (!this.isOpen) { + this.$btn.addClass("version-btn-open"); + this.$btn.attr("aria-pressed", true); + this.$dialog.attr("aria-hidden", false); + this.$dialog.fadeIn(speed, function() { + that.$btn.parent().on("focusout", function(e) { + that.focusoutHandler(); + e.stopImmediatePropagation(); + }) + that.$btn.parent().on("mouseleave", function(e) { + that.mouseoutHandler(); + e.stopImmediatePropagation(); + }); + }); + this.isOpen = true; + } + else { + this.$btn.removeClass("version-btn-open"); + this.$btn.attr("aria-pressed", false); + this.$dialog.attr("aria-hidden", true); + this.$btn.parent().off("focusout"); + this.$btn.parent().off("mouseleave"); + this.$dialog.fadeOut(speed, function() { + if (this.$sel) { + this.$sel.attr("tabindex", -1); + } + that.$btn.attr("tabindex", 0); + if (document.activeElement !== null && document.activeElement !== document && + document.activeElement !== document.body) { + that.$btn.focus(); + } + }); + this.isOpen = false; + } + + if (wasClose) { + if (this.$sel) { + this.$sel.attr("tabindex", -1); + } + if (document.activeElement !== null && document.activeElement !== document && + document.activeElement !== document.body) { + var $nw = this.listEnter(); + $nw.attr("tabindex", 0); + $nw.focus(); + this.$sel = $nw; + } + } + }, + btnOpenHandler : function() { + this.dialogToggle(300); + }, + focusoutHandler : function() { + var list = this.$list; + var that = this; + setTimeout(function() { + if (list.find(":focus").length === 0) { + that.dialogToggle(200); + } + }, 200); + }, + mouseoutHandler : function() { + this.dialogToggle(200); + }, + btnKeyFilter : function(e) { + if (e.ctrlKey || e.shiftKey) { + return false; + } + if (e.key === " " || e.key === "Enter" || (e.key === "ArrowDown" && e.altKey) || + e.key === "ArrowDown" || e.key === "ArrowUp") { + return true; + } + return false; + }, + keyMove : function(e) { + if (e.ctrlKey || e.shiftKey) { + return true; + } + var p = true; + var $nw = $(e.target); + switch (e.key) { + case "ArrowUp": + $nw = this.listPrev($nw); + break; + case "ArrowDown": + $nw = this.listNext($nw); + break; + case "Home": + $nw = this.listFirst(); + break; + case "End": + $nw = this.listLast(); + break; + case "Escape": + $nw = this.listExit(); + break; + case "ArrowLeft": + $nw = this.listExit(); + break; + case "ArrowRight": + $nw = this.listExit(); + break; + default: + p = false; + } + if (p) { + $nw.attr("tabindex", 0); + $nw.focus(); + if (this.$sel) { + this.$sel.attr("tabindex", -1); + } + this.$sel = $nw; + e.preventDefault(); + e.stopPropagation(); + } + }, + listPrev : function($nw) { + if ($nw.parent().prev().length !== 0) { + return $nw.parent().prev().children(":first-child"); + } + else { + return this.listLast(); + } + }, + listNext : function($nw) { + if ($nw.parent().next().length !== 0) { + return $nw.parent().next().children(":first-child"); + } + else { + return this.listFirst(); + } + }, + listFirst : function() { + return this.$list.children(":first-child").children(":first-child"); + }, + listLast : function() { + return this.$list.children(":last-child").children(":first-child"); + }, + listExit : function() { + this.mouseoutHandler(); + return this.$btn; + }, + listEnter : function() { + return this.$list.children(":first-child").children(":first-child"); + } + }; + return Popover +}(); + +$(document).ready(function() { + var lng_popover = new Popover("version-popover"); +}); +})(); diff --git a/doc/python_api/templates/versions.html b/doc/python_api/templates/versions.html new file mode 100644 index 00000000000..64b47185ba7 --- /dev/null +++ b/doc/python_api/templates/versions.html @@ -0,0 +1,17 @@ +
+ +
+ \ No newline at end of file