Initial commit of BudgetPro
@@ -0,0 +1 @@
|
||||
html/
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeDarkModeToggle extends HTMLElement {
|
||||
// SVG icons from https://fonts.google.com/icons
|
||||
// Licensed under the Apache 2.0 license:
|
||||
// https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
static lightModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FCBF00"><rect fill="none" height="24" width="24"/><circle cx="12" cy="12" opacity=".3" r="3"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>`
|
||||
static darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FE9700"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27 C17.45,17.19,14.93,19,12,19c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z" opacity=".3"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>`
|
||||
static title = "Toggle Light/Dark Mode"
|
||||
|
||||
static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
|
||||
static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
|
||||
|
||||
static _staticConstructor = function() {
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference)
|
||||
// Update the color scheme when the browsers preference changes
|
||||
// without user interaction on the website.
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
|
||||
})
|
||||
// Update the color scheme when the tab is made visible again.
|
||||
// It is possible that the appearance was changed in another tab
|
||||
// while this tab was in the background.
|
||||
document.addEventListener("visibilitychange", visibilityState => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
|
||||
}
|
||||
});
|
||||
}()
|
||||
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle')
|
||||
toggleButton.title = DoxygenAwesomeDarkModeToggle.title
|
||||
toggleButton.updateIcon()
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
toggleButton.updateIcon()
|
||||
})
|
||||
document.addEventListener("visibilitychange", visibilityState => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
toggleButton.updateIcon()
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
|
||||
})
|
||||
$(window).resize(function(){
|
||||
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onclick=this.toggleDarkMode
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` for dark-mode, `false` for light-mode system preference
|
||||
*/
|
||||
static get systemPreference() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` for dark-mode, `false` for light-mode user preference
|
||||
*/
|
||||
static get userPreference() {
|
||||
return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) ||
|
||||
(DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey))
|
||||
}
|
||||
|
||||
static set userPreference(userPreference) {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference
|
||||
if(!userPreference) {
|
||||
if(DoxygenAwesomeDarkModeToggle.systemPreference) {
|
||||
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true)
|
||||
} else {
|
||||
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)
|
||||
}
|
||||
} else {
|
||||
if(!DoxygenAwesomeDarkModeToggle.systemPreference) {
|
||||
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true)
|
||||
} else {
|
||||
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)
|
||||
}
|
||||
}
|
||||
DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged()
|
||||
}
|
||||
|
||||
static enableDarkMode(enable) {
|
||||
if(enable) {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = true
|
||||
document.documentElement.classList.add("dark-mode")
|
||||
document.documentElement.classList.remove("light-mode")
|
||||
} else {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = false
|
||||
document.documentElement.classList.remove("dark-mode")
|
||||
document.documentElement.classList.add("light-mode")
|
||||
}
|
||||
}
|
||||
|
||||
static onSystemPreferenceChanged() {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
|
||||
}
|
||||
|
||||
static onUserPreferenceChanged() {
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
|
||||
}
|
||||
|
||||
toggleDarkMode() {
|
||||
DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference
|
||||
this.updateIcon()
|
||||
}
|
||||
|
||||
updateIcon() {
|
||||
if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) {
|
||||
this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon
|
||||
} else {
|
||||
this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle);
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeFragmentCopyButton extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.onclick=this.copyContent
|
||||
}
|
||||
static title = "Copy to clipboard"
|
||||
static copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`
|
||||
static successIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`
|
||||
static successDuration = 980
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
if(navigator.clipboard) {
|
||||
const fragments = document.getElementsByClassName("fragment")
|
||||
for(const fragment of fragments) {
|
||||
const fragmentWrapper = document.createElement("div")
|
||||
fragmentWrapper.className = "doxygen-awesome-fragment-wrapper"
|
||||
const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button")
|
||||
fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
|
||||
fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title
|
||||
|
||||
fragment.parentNode.replaceChild(fragmentWrapper, fragment)
|
||||
fragmentWrapper.appendChild(fragment)
|
||||
fragmentWrapper.appendChild(fragmentCopyButton)
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
copyContent() {
|
||||
const content = this.previousSibling.cloneNode(true)
|
||||
// filter out line number from file listings
|
||||
content.querySelectorAll(".lineno, .ttc").forEach((node) => {
|
||||
node.remove()
|
||||
})
|
||||
let textContent = content.textContent
|
||||
// remove trailing newlines that appear in file listings
|
||||
let numberOfTrailingNewlines = 0
|
||||
while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') {
|
||||
numberOfTrailingNewlines++;
|
||||
}
|
||||
textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines)
|
||||
navigator.clipboard.writeText(textContent);
|
||||
this.classList.add("success")
|
||||
this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon
|
||||
window.setTimeout(() => {
|
||||
this.classList.remove("success")
|
||||
this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
|
||||
}, DoxygenAwesomeFragmentCopyButton.successDuration);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton)
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeInteractiveToc {
|
||||
static topOffset = 38
|
||||
static hideMobileMenu = true
|
||||
static headers = []
|
||||
|
||||
static init() {
|
||||
window.addEventListener("load", () => {
|
||||
let toc = document.querySelector(".contents > .toc")
|
||||
if(toc) {
|
||||
toc.classList.add("interactive")
|
||||
if(!DoxygenAwesomeInteractiveToc.hideMobileMenu) {
|
||||
toc.classList.add("open")
|
||||
}
|
||||
document.querySelector(".contents > .toc > h3")?.addEventListener("click", () => {
|
||||
if(toc.classList.contains("open")) {
|
||||
toc.classList.remove("open")
|
||||
} else {
|
||||
toc.classList.add("open")
|
||||
}
|
||||
})
|
||||
|
||||
document.querySelectorAll(".contents > .toc > ul a").forEach((node) => {
|
||||
let id = node.getAttribute("href").substring(1)
|
||||
DoxygenAwesomeInteractiveToc.headers.push({
|
||||
node: node,
|
||||
headerNode: document.getElementById(id)
|
||||
})
|
||||
|
||||
document.getElementById("doc-content")?.addEventListener("scroll", () => {
|
||||
DoxygenAwesomeInteractiveToc.update()
|
||||
})
|
||||
})
|
||||
DoxygenAwesomeInteractiveToc.update()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static update() {
|
||||
let active = DoxygenAwesomeInteractiveToc.headers[0]?.node
|
||||
DoxygenAwesomeInteractiveToc.headers.forEach((header) => {
|
||||
let position = header.headerNode.getBoundingClientRect().top
|
||||
header.node.classList.remove("active")
|
||||
header.node.classList.remove("aboveActive")
|
||||
if(position < DoxygenAwesomeInteractiveToc.topOffset) {
|
||||
active = header.node
|
||||
active?.classList.add("aboveActive")
|
||||
}
|
||||
})
|
||||
active?.classList.add("active")
|
||||
active?.classList.remove("aboveActive")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeParagraphLink {
|
||||
// Icon from https://fonts.google.com/icons
|
||||
// Licensed under the Apache 2.0 license:
|
||||
// https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
static icon = `<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z"/></svg>`
|
||||
static title = "Permanent Link"
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => {
|
||||
let anchorlink = document.createElement("a")
|
||||
anchorlink.setAttribute("href", `#${node.getAttribute("id")}`)
|
||||
anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title)
|
||||
anchorlink.classList.add("anchorlink")
|
||||
node.classList.add("anchor")
|
||||
anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon
|
||||
node.parentElement.appendChild(anchorlink)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 335px;
|
||||
--menu-display: none;
|
||||
|
||||
--top-height: 120px;
|
||||
--toc-sticky-top: -25px;
|
||||
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
|
||||
}
|
||||
|
||||
#projectname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#side-nav {
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
top: var(--top-height);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#nav-tree, #side-nav {
|
||||
height: calc(100vh - var(--top-height)) !important;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
height: var(--top-height);
|
||||
margin-bottom: calc(0px - var(--top-height));
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
overflow: hidden;
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
#main-nav {
|
||||
float: left;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
background: var(--separator-color);
|
||||
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||
}
|
||||
|
||||
#nav-path {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: var(--side-nav-fixed-width);
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
height: calc(100vh - 31px) !important;
|
||||
padding-bottom: calc(3 * var(--spacing-large));
|
||||
padding-top: calc(var(--top-height) - 80px);
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--side-nav-fixed-width) !important;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
left: var(--spacing-medium) !important;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 jothepro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeTabs {
|
||||
|
||||
static init() {
|
||||
window.addEventListener("load", () => {
|
||||
document.querySelectorAll(".tabbed:not(:empty)").forEach((tabbed, tabbedIndex) => {
|
||||
let tabLinkList = []
|
||||
tabbed.querySelectorAll(":scope > ul > li").forEach((tab, tabIndex) => {
|
||||
tab.id = "tab_" + tabbedIndex + "_" + tabIndex
|
||||
let header = tab.querySelector(".tab-title")
|
||||
let tabLink = document.createElement("button")
|
||||
tabLink.classList.add("tab-button")
|
||||
tabLink.appendChild(header)
|
||||
header.title = header.textContent
|
||||
tabLink.addEventListener("click", () => {
|
||||
tabbed.querySelectorAll(":scope > ul > li").forEach((tab) => {
|
||||
tab.classList.remove("selected")
|
||||
})
|
||||
tabLinkList.forEach((tabLink) => {
|
||||
tabLink.classList.remove("active")
|
||||
})
|
||||
tab.classList.add("selected")
|
||||
tabLink.classList.add("active")
|
||||
})
|
||||
tabLinkList.push(tabLink)
|
||||
if(tabIndex == 0) {
|
||||
tab.classList.add("selected")
|
||||
tabLink.classList.add("active")
|
||||
}
|
||||
})
|
||||
let tabsOverview = document.createElement("div")
|
||||
tabsOverview.classList.add("tabs-overview")
|
||||
let tabsOverviewContainer = document.createElement("div")
|
||||
tabsOverviewContainer.classList.add("tabs-overview-container")
|
||||
tabLinkList.forEach((tabLink) => {
|
||||
tabsOverview.appendChild(tabLink)
|
||||
})
|
||||
tabsOverviewContainer.appendChild(tabsOverview)
|
||||
tabbed.before(tabsOverviewContainer)
|
||||
|
||||
function resize() {
|
||||
let maxTabHeight = 0
|
||||
tabbed.querySelectorAll(":scope > ul > li").forEach((tab, tabIndex) => {
|
||||
let visibility = tab.style.display
|
||||
tab.style.display = "block"
|
||||
maxTabHeight = Math.max(tab.offsetHeight, maxTabHeight)
|
||||
tab.style.display = visibility
|
||||
})
|
||||
tabbed.style.height = `${maxTabHeight + 10}px`
|
||||
}
|
||||
|
||||
resize()
|
||||
new ResizeObserver(resize).observe(tabbed)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
static resize(tabbed) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
此文会详细介绍如何构建和引入SARibbon
|
||||
|
||||
# 准备工作
|
||||
|
||||
SARibbon使用了[QWindowkit](https://github.com/stdware/qwindowkit)作为无边框方案,同时也支持简单的无边框方案,如果你需要操作系统原生的窗口支持,如windows7以后的贴边处理,windows11的Snap Layout效果,建议开启[QWindowkit](https://github.com/stdware/qwindowkit)库,[QWindowkit](https://github.com/stdware/qwindowkit)库还能较好解决多屏幕移动问题
|
||||
|
||||
开启QWindowkit将能实现如下效果:
|
||||
|
||||

|
||||
|
||||
如果你要开启[QWindowkit](https://github.com/stdware/qwindowkit),需要先编译[QWindowkit](https://github.com/stdware/qwindowkit)库,[QWindowkit](https://github.com/stdware/qwindowkit)库作为SARibbon项目的submodules,如果在`git clone`时没有附带`--recursive`参数,需要执行`submodule update`命令:
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
# 编译QWindowkit库(如果不开启跳过此步)
|
||||
|
||||
`QWindowkit`库只提供了cmake的编译方式,必须使用cmake
|
||||
|
||||
为了简单,在`src/SARibbonBar/3rdparty`下提供了一个`CMakeLists.txt`文件,已经对此库的必要配置进行了设置,直接调用`src/SARibbonBar/3rdparty/CMakeLists.txt`文件编译即可
|
||||
|
||||
使用Qt Creator和使用visual studio构建和安装基本一样
|
||||
|
||||
## 使用Qt Creator构建和安装QWindowkit库
|
||||
|
||||
使用qt creator编译`QWindowkit`库,直接用qt creator打开`src/SARibbonBar/3rdparty/CMakeLists.txt`文件
|
||||
|
||||

|
||||
|
||||
点击运行(Ctrl+R)
|
||||
|
||||

|
||||
|
||||
切换到项目模式(Ctrl+5)
|
||||
|
||||
build步骤选择install(有些版本qt creator无法all和install一起选中,那么就先选all,编译完成后选install再执行安装)
|
||||
|
||||

|
||||
|
||||
再点击运行(Ctrl+R)
|
||||
|
||||
这时你会在SARibbon根目录下看到形如`bin_qt5.14.2_MSVC_x64`这样的安装目录,这里自动把`QWindowkit`库安装在此目录下
|
||||
|
||||

|
||||
|
||||
此时完成`QWindowkit`库的编译和安装
|
||||
|
||||
## 使用visual studio构建和安装QWindowkit库
|
||||
|
||||
使用visual studio编译`QWindowkit`库,用visual studio打开->CMake,选择`src/SARibbonBar/3rdparty/CMakeLists.txt`文件
|
||||
|
||||

|
||||
|
||||
选中CMake菜单->全部生成(有些版本没有CMake菜单,可以在CMakeLists.txt点右键)
|
||||
|
||||

|
||||
|
||||
选中CMake菜单->安装(有些版本没有CMake菜单,可以在CMakeLists.txt点右键)
|
||||
|
||||

|
||||
|
||||
> 不同的vs操作有点不一样,没有CMake菜单的,可以在CMakeLists.txt点右键
|
||||
|
||||

|
||||
|
||||
这时你会在SARibbon根目录下看到形如`bin_qt5.14.2_MSVC_x64`这样的安装目录,这里自动把`QWindowkit`库安装在此目录下
|
||||
|
||||

|
||||
|
||||
此时完成`QWindowkit`库的编译和安装
|
||||
|
||||
## 使用命令行构建(适用Qt5及vs2019以下)
|
||||
|
||||
由于`QWindowkit`库要求的cmake版本较高,vs2019及以下版本内置的cmake版本基本都无法满足,因此需要通过命令行对`QWindowkit`库进行构建,这里介绍在windows下如何通过cmd命令行构建`QWindowkit`库
|
||||
|
||||
首先你要安装一个高版本的cmake工具,假设安装在了`C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe`,同时你要确认你的qt版本路径和编译器,这里以Qt5.14.2 MSVC 2017版本举例
|
||||
|
||||
找到你qt的安装路径下Qt5Config.cmake所在的文件夹,例如:`C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\lib\cmake\Qt5`
|
||||
|
||||
打开命令行,cd到`src/SARibbonBar/3rdparty`目录,首先执行下面语句:
|
||||
|
||||
```
|
||||
"C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe" -B build -S . -G "Visual Studio 15 2017" -A x64 -DQt5_DIR="C:\Qt\Qt5.14.2\5.14.2\msvc2017_64\lib\cmake\Qt5"
|
||||
```
|
||||
|
||||
一般你的cmake.exe无法不在环境变量中,你在cmd命令中可以指定完整cmake路径,执行完后会看到如下输出:
|
||||
|
||||

|
||||
|
||||
接着你再执行下面两个命令即可
|
||||
|
||||
```
|
||||
"C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe" --build build --target install --config Debug
|
||||
"C:\Program Files (x86)\cmake3.27.9\bin\cmake.exe" --build build --target install --config Release
|
||||
```
|
||||
|
||||
# 构建SARibbonBar库
|
||||
|
||||
`SARibbonBar`库提供`cmake`和`qmake`两种方式构建,推荐使用`cmake`
|
||||
|
||||
> qt6之后不再维护`qmake`,逐渐转移到`cmake`中,`SARibbon`的未来版本不排除移除`qmake`
|
||||
|
||||
## 基于`CMake`构建`SARibbonBar`库
|
||||
|
||||
### vs下基于`cmake`的构建
|
||||
|
||||
如果要开启`QWindowKit`,在`CMakeLists.txt`中把`SARIBBON_USE_FRAMELESS_LIB`的option值手动改为ON
|
||||
|
||||
点击文件->打开->Cmake 选中CMakeLists.txt
|
||||
|
||||

|
||||
|
||||
将会形成如下的构建树
|
||||
|
||||

|
||||
|
||||
直接点击CMake菜单->全部生成(有些版本没有CMake菜单,可以在CMakeLists.txt点右键)
|
||||
|
||||

|
||||
|
||||
全部生成完成后,CMake菜单->安装->SARibbon(有些版本没有CMake菜单,可以在CMakeLists.txt点右键)
|
||||
|
||||

|
||||
|
||||
这时候你会看到源码的根目录下多出一个文件夹,文件夹命名方式为`bin_qt{version}_[MSVC/GNU]_x[64/86]`(你可以使用默认安装位置,把`SARIBBON_INSTALL_IN_CURRENT_DIR`变量设置为OFF即可:`SARIBBON_INSTALL_IN_CURRENT_DIR=OFF`)
|
||||
|
||||

|
||||
|
||||
### qtcreator下基于cmake的构建
|
||||
|
||||
点击文件->打开文件或项目选中CMakeLists.txt,加载完成后形成如下的构建树
|
||||
|
||||

|
||||
|
||||
Qt Creator可以在界面修改`SARIBBON_USE_FRAMELESS_LIB`值,也可以手动修改,在Qt Creator中,点击左侧边,切换到项目模式(Ctrl+5),如下图,在Current Configuration中设置`SARIBBON_USE_FRAMELESS_LIB`为ON即可开启`QWindowKit`,前提是要先编译`QWindowKit`
|
||||
|
||||

|
||||
|
||||
点击运行按钮
|
||||
|
||||

|
||||
|
||||
运行结束,会弹出例子窗口,点击左侧边栏的项目标签,Build的步骤,选中install
|
||||
|
||||

|
||||
|
||||
再次点击运行按钮,这时候你会看到源码的根目录下多出一个文件夹,文件夹命名方式为`bin_qt{version}_[MSVC/GNU]_x[64/86]`(前提是你没有改变CMAKE_INSTALL_PREFIX)
|
||||
|
||||

|
||||
|
||||
使用SARibbon的所有内容都在这个文件夹下
|
||||
|
||||
## 基于QMake构建SARibbonBar
|
||||
|
||||
qmake构建SARibbonBar只需使用Qt Creator打开`SARibbon.pro`文件即可
|
||||
|
||||
> 注意,如果使用Qt Creator打开`SARibbon.pro`文件过程报错,那么你的账户可能是没有足够的写权限,不同版本的Qt Creator在不同操作系统由不一样的表现,建议使用cmake
|
||||
|
||||
# 使用SARibbonBar库
|
||||
|
||||
## 基于cmake引入SARibbonBar库
|
||||
|
||||
首先要通过cmake编译并执行安装,在自己的工程CMakeLists.txt按照如下步骤执行:
|
||||
|
||||
1. 指定SARibbonBar的安装目录,把安装目录下的`lib/cmake/SARibbonBar`位置设置给`SARibbonBar_DIR`变量(CMake在执行`find_package(xx)`时,会先查看是否有`xx_DIR`变量,如果有,会先查询`xx_DIR`下是否有对应的`xxConfig.cmake`文件)
|
||||
|
||||
```cmake
|
||||
set(SARibbonBar_DIR "C:\src\Qt\SARibbon\bin_qt5.14.2_MSVC_x64\lib\cmake\SARibbonBar")
|
||||
```
|
||||
|
||||
2. 使用find_package找到SARibbonBar的Config文件,这个函数实际上是调用`lib/cmake/SARibbonBar/SARibbonBarConfig.cmake`文件,这里会把需要include的路径、预定义的宏,和需要添加的库给指定好,此时`SARibbonBar_INCLUDE_DIR`就是SARibbonBar的include文件路径
|
||||
|
||||
```cmake
|
||||
find_package(SARibbonBar)
|
||||
```
|
||||
|
||||
3. 最后调用`target_link_libraries`添加SARibbonBar库到自己的工程中,这里${myapp_target_name}是自己工程的target名字
|
||||
|
||||
```cmake
|
||||
target_link_libraries(${myapp_target_name} PUBLIC
|
||||
SARibbonBar
|
||||
)
|
||||
```
|
||||
|
||||
## 基于qmake引入SARibbonBar库
|
||||
|
||||
> Qt6开始,不再推荐使用`qmake`,SARibbon未来的版本有可能会取消qmake的支持
|
||||
|
||||
qmake的编译过程会在SARibbon下生成`bin_qt{Qt version}_{MSVC/GNU}_x{32/64}`文件夹,库文件和dll文件都在此文件夹下,importSARibbonBarLib.pri会自动把这个文件夹下的库引用进来,在引入之前需要先进行配置,配置内容位于`common.pri`中
|
||||
|
||||
步骤如下:
|
||||
|
||||
1. 先在你的工程中建立一个`3rdparty`文件夹,再把整个`SARibbon`文件夹拷贝过去
|
||||
|
||||
> SARibbon内部已经有几个pri文件可以很方便的让你把工程引入到自己目录中,`./importSARibbonBarLib.pri`文件就是用于引入SARibbon库的
|
||||
|
||||
importSARibbonBarLib.pri文件按照本库目录结构引入了依赖和头文件,如果你自己需要调整目录结构,可参考此文件进行修改
|
||||
|
||||
2. 配置common.pri文件
|
||||
|
||||
按照你实际的库的编译情况,配置SARibbon/common.pri文件,目前可选配置如下:
|
||||
|
||||
```shell
|
||||
# SA_RIBBON_CONFIG+=use_frameless
|
||||
# 此选项将使用frameless第三方库,这个选项在SARibbonBar.pri中会自动判断,如果,达到frameless的使用要求将会自动定义
|
||||
# frameless第三方库必须C++17且只有几个版本的qt可用,目前支持(qt5.14,qt5.15,qt6.4以上)
|
||||
# 除了上诉版本SA_RIBBON_CONFIG中不会加入use_frameless
|
||||
# frameless库能实现Ubuntu下和mac下的显示,同时多屏幕的支持也较好
|
||||
# 使用frameless库,需要定义QWindowKit的安装目录,默认在SARIBBON_BIN_DIR
|
||||
# SA_RIBBON_QWindowKit_Install_DIR = $$SARIBBON_BIN_DIR
|
||||
#
|
||||
# SA_RIBBON_CONFIG+=enable_snap_layout
|
||||
# 此选项将允许开启windows11的snap layout效果,目前在qt6.5下是正常显示位置,其它已知qt版本的snap layout位置会有偏移
|
||||
# 此选项必须在 SA_RIBBON_CONFIG+=use_frameless 下才有效
|
||||
```
|
||||
|
||||
你根据实际情况打开配置项(把注释去掉)
|
||||
|
||||
3. 在自己的Qt工程pro文件中加入如下语句即可
|
||||
|
||||
```shell
|
||||
include($$PWD/3rdparty/SARibbon/importSARibbonBarLib.pri)
|
||||
```
|
||||
|
||||
此时你的工程目录结构大致如下:
|
||||
|
||||
```
|
||||
|-[you-project-dir]
|
||||
| |-you-project.pro
|
||||
| |-[3rdparty]
|
||||
| |-[SARibbon](直接把SARibbon完整复制过来)
|
||||
| |-importSARibbonBarLib.pri(这个是方面你的qmake工程导入的文件,实际是引入了SARibbonBar.pri)
|
||||
| |-SARibbonBar.pri(用于引入库和依赖)
|
||||
| |-common.pri(这里是你的配置内容)
|
||||
| |-[bin_qtx.x.x_{MSVC/GNU}_x{32/64}]
|
||||
| |-[src]
|
||||
| | |-[SARibbonBar]
|
||||
```
|
||||
|
||||
`importSARibbonBarLib.pri`、`SARibbonBar.pri`、`common.pri`这三个文件是引入工程的关键文件
|
||||
|
||||
> 再次声明:Qt6.0版本后已经放弃qmake,建议使用cmake来管理工程
|
||||
|
||||
# 公开的预定义宏
|
||||
|
||||
SARibbon在编译过程中有些预定义宏,这些宏在基于visual studio的库引入是必须的
|
||||
|
||||
`SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1/0`,此宏用来定义是否引入了`QWindowkit`库
|
||||
|
||||
`SARIBBON_ENABLE_SNAP_LAYOUT=1/0`,此宏在SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=1时才有用,用于定义是否开始windows11的snap layout效果
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
This document will detail how to build and introduce SARibbon.
|
||||
|
||||
# Preparation
|
||||
|
||||
SARibbon uses [QWindowkit](https://github.com/stdware/qwindowkit) as a borderless solution, while also supporting a simpler approach. If you require native window support from the operating system, such as edge sticking in Windows 7 or later, and hover effects on the maximize button in Windows 11, it is recommended to enable the [QWindowkit](https://github.com/stdware/qwindowkit) library. The [QWindowkit](https://github.com/stdware/qwindowkit) library can also better handle multi-screen movement issues.
|
||||
|
||||
Enabling QWindowkit will achieve the following effects:
|
||||
|
||||

|
||||
|
||||
If you want to enable [QWindowkit](https://github.com/stdware/qwindowkit), you need to first compile the [QWindowkit](https://github.com/stdware/qwindowkit) library. As the [QWindowkit](https://github.com/stdware/qwindowkit) library is a submodule of the SARibbon project, if you did not include the `--recursive` parameter during `git clone`, you need to execute the `submodule update` command:
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
# Compile QWindowkit Library (Skip this step if not enabled)
|
||||
|
||||
`QWindowkit` library provides only cmake compilation method, and cmake must be used.
|
||||
|
||||
For simplicity, a `CMakeLists.txt` file is provided under `src/SARibbonBar/3rdparty`, which has already set up the necessary configurations for this library. Simply call the `src/SARibbonBar/3rdparty/CMakeLists.txt` file to compile.
|
||||
|
||||
Building with Qt Creator is similar to using Visual Studio.
|
||||
|
||||
## Build and Install QWindowkit Library with Qt Creator
|
||||
|
||||
Compile the `QWindowkit` library using Qt Creator by directly opening the `src/SARibbonBar/3rdparty/CMakeLists.txt` file.
|
||||
|
||||

|
||||
|
||||
Click run (Ctrl+R).
|
||||
|
||||

|
||||
|
||||
Switch to the Projects mode (Ctrl+5).
|
||||
|
||||
Select the 'install' option for the build step.
|
||||
|
||||

|
||||
|
||||
Click run again (Ctrl+R).
|
||||
|
||||
You will now see an installation directory like `bin_qt5.14.2_MSVC_x64` in the SARibbon root directory. The `QWindowkit` library is automatically installed in this directory.
|
||||
|
||||

|
||||
|
||||
This completes the compilation and installation of the `QWindowkit` library.
|
||||
|
||||
## Build and Install QWindowkit Library with Visual Studio
|
||||
|
||||
Compile the `QWindowkit` library using Visual Studio by opening CMake and selecting the `src/SARibbonBar/3rdparty/CMakeLists.txt` file.
|
||||
|
||||

|
||||
|
||||
Select CMake menu->Generate All.
|
||||
|
||||

|
||||
|
||||
Select CMake menu->Install.
|
||||
|
||||

|
||||
|
||||
You will now see an installation directory like `bin_qt5.14.2_MSVC_x64` in the SARibbon root directory. The `QWindowkit` library is automatically installed in this directory.
|
||||
|
||||

|
||||
|
||||
This completes the compilation and installation of the `QWindowkit` library.
|
||||
|
||||
# Build SARibbonBar Library
|
||||
|
||||
SARibbonBar library provides two ways to build: cmake and qmake. It is recommended to use cmake.
|
||||
|
||||
## Build SARibbonBar Library with CMake
|
||||
|
||||
### Building with CMake in Visual Studio
|
||||
|
||||
To enable `QWindowKit`, manually set the value of the `SARIBBON_USE_FRAMELESS_LIB` option to ON in the CMakeLists.txt file:
|
||||
|
||||
```
|
||||
option(SARIBBON_USE_FRAMELESS_LIB "Using the QWindowKit library as a frameless solution" ON)
|
||||
```
|
||||
|
||||
Open file->Open->CMake and select CMakeLists.txt.
|
||||
|
||||

|
||||
|
||||
The build tree will look like this:
|
||||
|
||||

|
||||
|
||||
Click CMake menu->Generate All.
|
||||
|
||||

|
||||
|
||||
After generation, click CMake menu->Install->SARibbon.
|
||||
|
||||

|
||||
|
||||
You will see a new folder in the root directory named `bin_qt{version}_{MSVC/GNU}_x{64/86}` (assuming you did not change CMAKE_INSTALL_PREFIX).
|
||||
|
||||

|
||||
|
||||
### Building with CMake in Qt Creator
|
||||
|
||||
Click File->Open File or Project and select CMakeLists.txt. Once loaded, the project tree will appear as follows:
|
||||
|
||||

|
||||
|
||||
In Qt Creator, you can modify the value of `SARIBBON_USE_FRAMELESS_LIB` in the interface or manually. Switch to the Projects mode (Ctrl+5), navigate to Current Configuration, and set `SARIBBON_USE_FRAMELESS_LIB` to ON to enable `QWindowKit`, assuming you have compiled `QWindowKit` first.
|
||||
|
||||

|
||||
|
||||
Click the run button.
|
||||
|
||||

|
||||
|
||||
After completion, a sample window will pop up. Click the Build steps in the sidebar, and select install.
|
||||
|
||||

|
||||
|
||||
Click run again, and you will see a new folder in the root directory named `bin_qt{version}_{MSVC/GNU}_x{64/86}` (assuming you did not change CMAKE_INSTALL_PREFIX).
|
||||
|
||||

|
||||
|
||||
All contents related to using SARibbon are located in this folder.
|
||||
|
||||
## Build SARibbonBar Library with QMake
|
||||
|
||||
Building SARibbonBar with qmake only requires opening the SARibbon.pro file in Qt Creator.
|
||||
|
||||
Note: If there are errors during the process of opening the SARibbon.pro file in Qt Creator, your account may lack sufficient write permissions. Different versions of Qt Creator exhibit different behaviors on different operating systems. It is recommended to use cmake.
|
||||
|
||||
# Using SARibbonBar Library
|
||||
|
||||
## Introducing SARibbonBar with CMake
|
||||
|
||||
First, compile and install SARibbonBar using cmake in your project:
|
||||
|
||||
1. Specify the SARibbonBar installation directory and set the `SARibbonBar_DIR` variable to point to the `lib/cmake/SARibbonBar` location in the installation directory.
|
||||
|
||||
```cmake
|
||||
set(SARibbonBar_DIR "C:\src\Qt\SARibbon\bin_qt5.14.2_MSVC_x64\lib\cmake\SARibbonBar")
|
||||
```
|
||||
|
||||
2. Use `find_package` to locate the SARibbonBar Config file. This function actually calls the `lib/cmake/SARibbonBar/SARibbonBarConfig.cmake` file, which sets up the necessary include paths, predefined macros, and libraries to add. At this point, `SARibbonBar_INCLUDE_DIR` is the path to the SARibbonBar include files.
|
||||
|
||||
```cmake
|
||||
find_package(SARibbonBar)
|
||||
```
|
||||
|
||||
3. Finally, use `target_link_libraries` to add the SARibbonBar library to your project. Here, `${myapp_target_name}` is the target name of your project.
|
||||
|
||||
```cmake
|
||||
target_link_libraries(${myapp_target_name} PUBLIC
|
||||
SARibbonBar
|
||||
)
|
||||
```
|
||||
|
||||
## Introducing SARibbonBar with QMake
|
||||
|
||||
During the qmake compilation process, a `bin_qt{Qt version}_{MSVC/GNU}_x{32/64}` folder will be generated under SARibbon, containing library and dll files. The `importSARibbonBarLib.pri` file will automatically reference the libraries in this folder.
|
||||
|
||||
Follow these steps:
|
||||
|
||||
1. Create a `3rdparty` folder in your project and copy the entire SARibbon folder into it.
|
||||
|
||||
> Inside SARibbon, there are several `.pri` files that can easily integrate the project into your directory. The `importSARibbonBarLib.pri` file is used to import the SARibbon library.
|
||||
|
||||
2. Add the following statement to your project's Qt project pro file:
|
||||
|
||||
```shell
|
||||
include($$PWD/3rdparty/SARibbon/importSARibbonBarLib.pri)
|
||||
```
|
||||
|
||||
Your project directory structure should look roughly like this:
|
||||
|
||||
```
|
||||
|-[you-project-dir]
|
||||
| |-you-project.pro
|
||||
| |-[3rdparty]
|
||||
| |-[SARibbon](Copy SARibbon entirely here)
|
||||
| |-importSARibbonBarLib.pri
|
||||
| |-SARibbonBar.pri
|
||||
| |-common.pri
|
||||
| |-[bin_qtx.x.x_{MSVC/GNU}_x{32/64}]
|
||||
| |-[src]
|
||||
| | |-[SARibbonBar]
|
||||
```
|
||||
|
||||
These three files (`importSARibbonBarLib.pri`, `SARibbonBar.pri`, `common.pri`) are crucial for integrating the project.
|
||||
|
||||
> Note: Starting from Qt 6.0, qmake has been abandoned. It is recommended to use cmake to manage projects.
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 108 KiB |
@@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2023-05-28T13:48:40.826Z" agent="5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/19.0.3 Chrome/102.0.5005.63 Electron/19.0.3 Safari/537.36" etag="VUcCu14FYz5cpfwmjXz9" version="19.0.3" type="device"><diagram id="yGERRYrwaGF77KkQtr55" name="RibbonToolButton">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</diagram></mxfile>
|
||||
@@ -0,0 +1,60 @@
|
||||
Ribbon界面无法用普通的tab+toolbutton组合来实现主要就是因为ribbon界面对工具按钮有特殊的渲染方式,和经典菜单按钮是有很大不同的,在没有菜单的情况下没有什么区别,但有菜单的情况下,会有明显不同
|
||||
|
||||
差别主要通过下面这个动图可以提现,尤其是在MenuPopup模式下,按钮会被拆分为两部分,普通的工具栏是左右拆分,而ribbon的工具按钮是上下拆分
|
||||
|
||||

|
||||
|
||||
# ribbon工具栏按钮绘制方案
|
||||
|
||||
`SARibbon`的工具栏按钮为`SARibbonToolButton`,它继承`QToolButton`,重写了其绘图方法
|
||||
|
||||
工具栏按钮分3个矩形区域,一个是icon绘制区域,一个是文本显示区域,一个是indicator绘制区域
|
||||
|
||||
三个区域会有两种布局方案,根据文本是否换行来进行布局
|
||||
|
||||
## 文字换行显示的布局方案
|
||||
|
||||
换行模式下,文本区域会占据两行行高,office的word就是这种布局方案
|
||||
|
||||

|
||||
|
||||
可以看到,第一个pannel文本没有换行,会有一行的留白,第二个pannel文本都换行,充满了文本区域,且能看到菜单的箭头在第二行最右边
|
||||
|
||||
对于没有换行的文本,但有菜单的情况下,indicator在第二行文本处
|
||||
|
||||

|
||||
|
||||
下图为`SARibbonToolButton`文本换行的布局方案
|
||||
|
||||

|
||||
|
||||
indicator是有菜单时的下箭头提示,在文本确切要换行时,下箭头在第二行文本的最右边,若在换行模式,但文本没有换行,第二行的位置就用来显示indicator
|
||||
|
||||
> 注意,如果文本太长,`SARibbonToolButton`会尝试换行,由于英文的文本每个单词不是定长,因此会在原有文本单行显示的长度基础上先尝试1/2的长度能否换行完全显示,如果显示不了,就把长度递增为1/2+1/3,并继续尝试,如果3次都无法容纳下,就显示为原来的长度
|
||||
|
||||
> 注意,换行模式下,用户可以手动给文本换行,就是加入`\n`换行,加入`\n`的文本,`SARibbonToolButton`就不用进行换行估算
|
||||
|
||||
> 注意,带有`\n`的文本在不换行模式下也可正常显示,此时`\n`会被忽略掉
|
||||
|
||||
## 文字不换行显示的布局方案
|
||||
|
||||
对于不换行的方案,最经典的就是wps的布局方案,wps-word所有文字都是一行,不存在换行的情况,indicator布置在最右边
|
||||
|
||||

|
||||
|
||||
wps的方案尤其适合显示中文文本,但英文会有可能放不下,或者英文太长,单行很难显示全,因此如果要显示英文,建议使用换行方案,要显示中文,建议使用不换行方案
|
||||
|
||||
`SARibbonToolButton`不换行的布局方案如下
|
||||
|
||||

|
||||
|
||||
# 如何布置一个更美观的Ribbon界面
|
||||
|
||||
如果文字都比较短,又没有菜单情况下,使用换行模式显示会有一行文本的留白,如果很多的话会不太美观
|
||||
|
||||

|
||||
|
||||
此时又如下几种方案:
|
||||
|
||||
- 大小搭配,如2个大按钮搭配3个小按钮
|
||||
- 使用\n强制换行,尤其是4个文字的情况下可以两个两个
|
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 632 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 529 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 574 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 471 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 26 KiB |