| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- /*
- @licstart The following is the entire license notice for the JavaScript code in this file.
- The MIT License (MIT)
- Copyright (C) 1997-2020 by Dimitri van Heesch
- 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.
- @licend The above is the entire license notice for the JavaScript code in this file
- */
- function initMenu(relPath,treeview) {
- const SHOW_DELAY = 250; // 250ms delay before showing
- const HIDE_DELAY = 500; // 500ms delay before hiding
- const SLIDE_DELAY = 250; // 250ms slide up/down delay
- const WHEEL_STEP = 30; // 30 pixel per mouse wheel tick
- const ARROW_STEP = 5; // 5 pixel when hovering arrow up/down
- const ARROW_POLL_INTERVAL = 20; // 20ms per arrow up/down check
- const MOBILE_WIDTH = 768; // switch point for mobile/desktop mode
- // Helper function for slideDown animation
- function slideDown(element, duration, callback) {
- if (element.dataset.animating) return;
- element.dataset.animating = 'true';
- element.style.removeProperty('display');
- let display = window.getComputedStyle(element).display;
- if (display === 'none') display = 'block';
- element.style.display = display;
- const height = element.offsetHeight;
- element.style.overflow = 'hidden';
- element.style.height = 0;
- element.offsetHeight; // force reflow
- element.style.transitionProperty = 'height';
- element.style.transitionDuration = duration + 'ms';
- element.style.height = height + 'px';
- window.setTimeout(() => {
- element.style.removeProperty('height');
- element.style.removeProperty('overflow');
- element.style.removeProperty('transition-duration');
- element.style.removeProperty('transition-property');
- delete element.dataset.animating;
- if (callback) callback();
- }, duration);
- }
- // Helper function for slideUp animation
- function slideUp(element, duration, callback) {
- if (element.dataset.animating) return;
- element.dataset.animating = 'true';
- element.style.transitionProperty = 'height';
- element.style.transitionDuration = duration + 'ms';
- element.style.height = element.offsetHeight + 'px';
- element.offsetHeight; // force reflow
- element.style.overflow = 'hidden';
- element.style.height = 0;
- window.setTimeout(() => {
- element.style.display = 'none';
- element.style.removeProperty('height');
- element.style.removeProperty('overflow');
- element.style.removeProperty('transition-duration');
- element.style.removeProperty('transition-property');
- delete element.dataset.animating;
- if (callback) callback();
- }, duration);
- }
- // Helper to create the menu tree structure
- function makeTree(data,relPath,topLevel=false) {
- let result='';
- if ('children' in data) {
- if (!topLevel) {
- result+='<ul>';
- }
- for (let i in data.children) {
- let url;
- const link = data.children[i].url;
- if (link.substring(0,1)=='^') {
- url = link.substring(1);
- } else {
- url = relPath+link;
- }
- result+='<li><a href="'+url+'">'+
- data.children[i].text+'</a>'+
- makeTree(data.children[i],relPath)+'</li>';
- }
- if (!topLevel) {
- result+='</ul>';
- }
- }
- return result;
- }
- const mainNav = document.getElementById('main-nav');
- if (mainNav && mainNav.children.length > 0) {
- const firstChild = mainNav.children[0];
- firstChild.insertAdjacentHTML('afterbegin', makeTree(menudata, relPath, true));
- }
- const searchBoxPos2 = document.getElementById('searchBoxPos2');
- let searchBoxContents = searchBoxPos2 ? searchBoxPos2.innerHTML : '';
- const mainMenuState = document.getElementById('main-menu-state');
- let prevWidth = 0;
- const initResizableIfExists = function() {
- if (typeof initResizableFunc === 'function') initResizableFunc(treeview);
- }
- // Dropdown menu functionality to replace smartmenus
- let closeAllDropdowns = null; // Will be set by initDropdownMenu
- const isMobile = () => window.innerWidth < MOBILE_WIDTH;
- if (mainMenuState) {
- const mainMenu = document.getElementById('main-menu');
- const searchBoxPos1 = document.getElementById('searchBoxPos1');
- // animate mobile main menu
- mainMenuState.addEventListener('change', function() {
- if (this.checked) {
- slideDown(mainMenu, SLIDE_DELAY, () => {
- mainMenu.style.display = 'block';
- initResizableIfExists();
- });
- } else {
- slideUp(mainMenu, SLIDE_DELAY, () => {
- mainMenu.style.display = 'none';
- initResizableIfExists();
- });
- }
- });
- // set default menu visibility
- const resetState = function() {
- const newWidth = window.innerWidth;
- if (newWidth !== prevWidth) {
- // Close all open dropdown menus when switching between mobile/desktop modes
- if (closeAllDropdowns) {
- closeAllDropdowns();
- }
- if (newWidth < MOBILE_WIDTH) {
- mainMenuState.checked = false;
- mainMenu.style.display = 'none';
- if (searchBoxPos2) {
- searchBoxPos2.innerHTML = '';
- searchBoxPos2.style.display = 'none';
- }
- if (searchBoxPos1) {
- searchBoxPos1.innerHTML = searchBoxContents;
- searchBoxPos1.style.display = '';
- }
- } else {
- mainMenu.style.display = '';
- if (searchBoxPos1) {
- searchBoxPos1.innerHTML = '';
- searchBoxPos1.style.display = 'none';
- }
- if (searchBoxPos2) {
- searchBoxPos2.innerHTML = searchBoxContents;
- searchBoxPos2.style.display = '';
- }
- }
- if (typeof searchBox !== 'undefined') {
- searchBox.CloseResultsWindow();
- }
- prevWidth = newWidth;
- }
- }
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', function() {
- resetState();
- initResizableIfExists();
- });
- } else {
- resetState();
- initResizableIfExists();
- }
- window.addEventListener('resize', resetState);
- } else {
- initResizableIfExists();
- }
- function initDropdownMenu() {
- const mainMenu = document.getElementById('main-menu');
- if (!mainMenu) return;
- const menuItems = mainMenu.querySelectorAll('li');
- // Helper function to position nested submenu with viewport checking
- function positionNestedSubmenu(submenu, link) {
- const viewport = {
- height: window.innerHeight,
- scrollY: window.scrollY
- };
- // Set initial position - top aligned with parent (next to arrow)
- submenu.style.top = '0';
- if (isMobile()) {
- submenu.style.marginLeft = 0;
- } else {
- submenu.style.marginLeft = link.offsetWidth + 'px';
- }
- // Get submenu dimensions and position
- const submenuRect = submenu.getBoundingClientRect();
- const submenuHeight = submenuRect.height;
- const submenuTop = submenuRect.top;
- const submenuBottom = submenuRect.bottom+1; // add space for border
- // Check if submenu fits in viewport
- const fitsAbove = submenuTop >= 0;
- const fitsBelow = submenuBottom <= viewport.height;
- if (!fitsAbove || !fitsBelow) {
- // Submenu doesn't fit - try to adjust position
- // Overflows bottom, try to shift up
- const overflow = submenuBottom - viewport.height;
- const newTop = Math.max(0, submenuTop-overflow)-submenuTop;
- submenu.style.top = newTop + 'px';
- // Re-check after adjustment
- const adjustedRect = submenu.getBoundingClientRect();
- if (adjustedRect.height > viewport.height) {
- // Still doesn't fit - enable scrolling
- enableSubmenuScrolling(submenu, link);
- }
- }
- }
- // Helper function to enable scrolling for tall submenus
- function enableSubmenuScrolling(submenu, link) {
- // Check if scroll arrows already exist
- if (submenu.dataset.scrollEnabled) return;
- submenu.dataset.scrollEnabled = 'true';
- const viewport = {
- height: window.innerHeight,
- scrollY: window.scrollY
- };
- // Position submenu to fill available viewport space
- const parentRect = link.getBoundingClientRect();
- const availableHeight = viewport.height - 2; // Leave some margin
- submenu.style.maxHeight = availableHeight + 'px';
- submenu.style.overflow = 'hidden';
- submenu.style.position = 'absolute';
- // Create scroll arrows
- const scrollUpArrow = document.createElement('div');
- scrollUpArrow.className = 'submenu-scroll-arrow submenu-scroll-up';
- scrollUpArrow.innerHTML = '<span class="scroll-up-arrow"></span>';//'<span>▲</span>';
- scrollUpArrow.style.cssText = 'position:absolute;top:0;left:0;right:0;height:30px;background:transparent;text-align:center;line-height:30px;color:#fff;cursor:pointer;z-index:1000;display:none;';
- const scrollDownArrow = document.createElement('div');
- scrollDownArrow.className = 'submenu-scroll-arrow submenu-scroll-down';
- scrollDownArrow.innerHTML = '<span class="scroll-down-arrow"></span>';
- scrollDownArrow.style.cssText = 'position:absolute;bottom:0;left:0;right:0;height:30px;background:transparent;text-align:center;line-height:30px;color:#fff;cursor:pointer;z-index:1000;';
- // Create wrapper for submenu content
- const scrollWrapper = document.createElement('div');
- scrollWrapper.className = 'submenu-scroll-wrapper';
- scrollWrapper.style.cssText = 'height:100vh;overflow:hidden;position:relative;';
- // Move submenu children to wrapper
- while (submenu.firstChild) {
- scrollWrapper.appendChild(submenu.firstChild);
- }
- submenu.appendChild(scrollUpArrow);
- submenu.appendChild(scrollWrapper);
- submenu.appendChild(scrollDownArrow);
- let scrollPosition = 0;
- let scrollInterval = null;
- function updateScrollArrows() {
- const maxScroll = scrollWrapper.scrollHeight - availableHeight;
- scrollUpArrow.style.display = scrollPosition > 0 ? 'block' : 'none';
- scrollDownArrow.style.display = scrollPosition < maxScroll ? 'block' : 'none';
- }
- function startScrolling(direction) {
- if (scrollInterval) return;
- scrollInterval = setInterval(() => {
- const maxScroll = scrollWrapper.scrollHeight - availableHeight;
- if (direction === 'up') {
- scrollPosition = Math.max(0, scrollPosition - ARROW_STEP);
- } else {
- scrollPosition = Math.min(maxScroll, scrollPosition + ARROW_STEP);
- }
- scrollWrapper.scrollTop = scrollPosition;
- updateScrollArrows();
- if ((direction === 'up' && scrollPosition === 0) ||
- (direction === 'down' && scrollPosition === maxScroll)) {
- stopScrolling();
- }
- }, ARROW_POLL_INTERVAL);
- }
- function stopScrolling() {
- if (scrollInterval) {
- clearInterval(scrollInterval);
- scrollInterval = null;
- }
- }
- scrollUpArrow.addEventListener('mouseenter', () => startScrolling('up'));
- scrollUpArrow.addEventListener('mouseleave', stopScrolling);
- scrollDownArrow.addEventListener('mouseenter', () => startScrolling('down'));
- scrollDownArrow.addEventListener('mouseleave', stopScrolling);
- function wheelEvent(e) {
- e.preventDefault();
- e.stopPropagation();
- const maxScroll = scrollWrapper.scrollHeight - availableHeight;
- const wheelDelta = e.deltaY;
- const scrollAmount = wheelDelta > 0 ? WHEEL_STEP : -WHEEL_STEP; // Scroll 30px per wheel tick
- scrollPosition = Math.max(0, Math.min(maxScroll, scrollPosition + scrollAmount));
- scrollWrapper.scrollTop = scrollPosition;
- updateScrollArrows();
- }
- // Add mouse wheel scrolling support
- scrollWrapper.addEventListener('wheel', (e) => wheelEvent(e));
- // Also add wheel event to submenu itself to catch events
- submenu.addEventListener('wheel', function(e) {
- // Only handle if scrolling is enabled
- if (submenu.dataset.scrollEnabled) {
- wheelEvent(e);
- }
- });
- // Initial arrow state
- updateScrollArrows();
- }
- // Helper function to clean up scroll arrows
- function disableSubmenuScrolling(submenu) {
- if (!submenu.dataset.scrollEnabled) return;
- delete submenu.dataset.scrollEnabled;
- // Find and remove scroll elements
- const scrollArrows = submenu.querySelectorAll('.submenu-scroll-arrow');
- const scrollWrapper = submenu.querySelector('.submenu-scroll-wrapper');
- if (scrollWrapper) {
- // Move children back to submenu
- while (scrollWrapper.firstChild) {
- submenu.appendChild(scrollWrapper.firstChild);
- }
- scrollWrapper.remove();
- }
- scrollArrows.forEach(arrow => arrow.remove());
- // Reset styles
- submenu.style.maxHeight = '';
- submenu.style.overflow = '';
- }
- menuItems.forEach(item => {
- const submenu = item.querySelector('ul');
- if (submenu) {
- const link = item.querySelector('a');
- if (link) {
- // Add class and ARIA attributes for accessibility
- link.classList.add('has-submenu');
- link.setAttribute('aria-haspopup', 'true');
- link.setAttribute('aria-expanded', 'false');
- // Add sub-arrow indicator
- const span = document.createElement('span');
- span.classList.add('sub-arrow');
- link.append(span);
- // Calculate nesting level for z-index
- // Root menu (main-menu) is level 200 (above the search box at 102),
- // first submenus are level 201, etc.
- let nestingLevel = 200;
- let currentElement = item.parentElement;
- while (currentElement && currentElement.id !== 'main-menu') {
- if (currentElement.tagName === 'UL') {
- nestingLevel++;
- }
- currentElement = currentElement.parentElement;
- }
- // Apply z-index based on nesting level
- // This ensures child menus with shadows appear above parent menus
- submenu.style.zIndex = nestingLevel + 1;
- // Check if this is a level 2+ submenu (nested within another dropdown)
- const isNestedSubmenu = item.parentElement && item.parentElement.id !== 'main-menu';
- // Timeout management for smooth menu navigation
- let showTimeout = null;
- let hideTimeout = null;
- // Desktop: show on hover
- item.addEventListener('mouseenter', function() {
- if (!isMobile()) {
- // Clear any pending hide timeout
- if (hideTimeout) {
- clearTimeout(hideTimeout);
- hideTimeout = null;
- }
- // Set show timeout
- showTimeout = setTimeout(() => {
- // Hide all sibling menus at the same level before showing this one
- const parentElement = item.parentElement;
- if (parentElement) {
- const siblings = parentElement.querySelectorAll(':scope > li');
- siblings.forEach(sibling => {
- if (sibling !== item) {
- const siblingSubmenu = sibling.querySelector('ul');
- const siblingLink = sibling.querySelector('a');
- if (siblingSubmenu && siblingLink) {
- siblingSubmenu.style.display = 'none';
- siblingLink.setAttribute('aria-expanded', 'false');
- disableSubmenuScrolling(siblingSubmenu);
- }
- }
- });
- }
- submenu.style.display = 'block';
- // Only apply positioning for nested submenus (level 2+)
- if (isNestedSubmenu) {
- positionNestedSubmenu(submenu, link);
- }
- link.setAttribute('aria-expanded', 'true');
- showTimeout = null;
- }, SHOW_DELAY);
- }
- });
- item.addEventListener('mouseleave', function() {
- if (!isMobile()) {
- // Clear any pending show timeout
- if (showTimeout) {
- clearTimeout(showTimeout);
- showTimeout = null;
- }
- // Set hide timeout
- hideTimeout = setTimeout(() => {
- submenu.style.display = 'none';
- link.setAttribute('aria-expanded', 'false');
- // Clean up scrolling if enabled
- disableSubmenuScrolling(submenu);
- hideTimeout = null;
- }, HIDE_DELAY);
- }
- });
- if (isMobile() && isNestedSubmenu) {
- positionNestedSubmenu(submenu, link);
- }
- function toggleMenu() {
- const isExpanded = link.getAttribute('aria-expanded') === 'true';
- if (isExpanded) {
- slideUp(submenu, SLIDE_DELAY, () => {
- submenu.style.display = 'none';
- link.setAttribute('aria-expanded', 'false');
- link.classList.remove('highlighted')
- disableSubmenuScrolling(submenu);
- });
- } else {
- slideDown(submenu, SLIDE_DELAY, () => {
- submenu.style.display = 'block';
- link.classList.add('highlighted')
- link.setAttribute('aria-expanded', 'true');
- });
- }
- }
- // Mobile/Touch: toggle on click
- link.addEventListener('click', function(e) {
- if (isMobile()) {
- e.preventDefault();
- toggleMenu();
- }
- });
- // Keyboard navigation
- link.addEventListener('keydown', function(e) {
- if (e.key === 'Enter' || e.key === ' ') {
- e.preventDefault();
- toggleMenu();
- } else if (e.key === 'Escape') {
- submenu.style.display = 'none';
- link.setAttribute('aria-expanded', 'false');
- disableSubmenuScrolling(submenu);
- link.focus();
- }
- });
- }
- }
- });
- // Helper function to close all open dropdown menus
- closeAllDropdowns = function() {
- menuItems.forEach(item => {
- const submenu = item.querySelector('ul');
- const link = item.querySelector('a.has-submenu');
- if (submenu && link) {
- disableSubmenuScrolling(submenu);
- submenu.style.display = 'none';
- submenu.style.marginLeft = 0;
- link.setAttribute('aria-expanded', 'false');
- link.classList.remove('highlighted');
- }
- });
- };
- // Close all dropdown menus when clicking a link (navigation to new page or anchor)
- const allLinks = mainMenu.querySelectorAll('a');
- allLinks.forEach(link => {
- link.addEventListener('click', function() {
- // Close dropdowns when navigating (unless it's a has-submenu link in mobile mode)
- if (!link.classList.contains('has-submenu') || !isMobile()) {
- if (closeAllDropdowns) {
- closeAllDropdowns();
- }
- }
- });
- });
- }
- // Initialize dropdown menu behavior
- initDropdownMenu();
- // Close all open menus when browser back button is pressed
- window.addEventListener('popstate', function() {
- if (closeAllDropdowns) {
- closeAllDropdowns();
- }
- });
- }
- document.addEventListener('DOMContentLoaded', () => {
- const configElem = document.getElementById('doxygen-config');
- if (configElem) {
- const config = JSON.parse(configElem.textContent);
- initMenu(config.relPath, config.generateTreeView);
- if (config.searchEngine) {
- if (!config.serverBasedSearch) {
- if (!config.disableIndex && config.dynamicMenus && !config.fullSidebar) {
- if (typeof init_search === 'function') init_search();
- }
- } else {
- if (document.querySelector('.searchresults') && typeof searchBox !== 'undefined') {
- searchBox.DOMSearchField().focus();
- }
- }
- }
- }
- });
- /* @license-end */
|