1/**
2 * Copyright (c) 2020-2023, The Godot community
3 * Copyright (c) 2023, Benjamin Cabé <benjamin@zephyrproject.org>
4 * SPDX-License-Identifier: CC-BY-3.0
5 */
6
7
8// Handle page scroll and adjust sidebar accordingly.
9
10// Each page has two scrolls: the main scroll, which is moving the content of the page;
11// and the sidebar scroll, which is moving the navigation in the sidebar.
12// We want the logo to gradually disappear as the main content is scrolled, giving
13// more room to the navigation on the left. This means adjusting the height
14// available to the navigation on the fly.
15const registerOnScrollEvent = (function(){
16        // Configuration.
17
18        // The number of pixels the user must scroll by before the logo is completely hidden.
19        const scrollTopPixels = 156;
20        // The target margin to be applied to the navigation bar when the logo is hidden.
21        const menuTopMargin = 54;
22        // The max-height offset when the logo is completely visible.
23        const menuHeightOffset_default = 210;
24        // The max-height offset when the logo is completely hidden.
25        const menuHeightOffset_fixed = 63;
26        // The distance between the two max-height offset values above; used for intermediate values.
27        const menuHeightOffset_diff = (menuHeightOffset_default - menuHeightOffset_fixed);
28
29        // Media query handler.
30        return function(mediaQuery) {
31          // We only apply this logic to the "desktop" resolution (defined by a media query at the bottom).
32          // This handler is executed when the result of the query evaluation changes, which means that
33          // the page has moved between "desktop" and "mobile" states.
34
35          // When entering the "desktop" state, we register scroll events and adjust elements on the page.
36          // When entering the "mobile" state, we clean up any registered events and restore elements on the page
37          // to their initial state.
38
39          const $window = $(window);
40          const $sidebar = $('.wy-side-scroll');
41          const $search = $sidebar.children('.wy-side-nav-search');
42          const $menu = $sidebar.children('.wy-menu-vertical');
43
44          if (mediaQuery.matches) {
45            // Entering the "desktop" state.
46
47            // The main scroll event handler.
48            // Executed as the page is scrolled and once immediately as the page enters this state.
49            const handleMainScroll = (currentScroll) => {
50              if (currentScroll >= scrollTopPixels) {
51                // After the page is scrolled below the threshold, we fix everything in place.
52                $search.css('margin-top', `-${scrollTopPixels}px`);
53                $menu.css('margin-top', `${menuTopMargin}px`);
54                $menu.css('max-height', `calc(100% - ${menuHeightOffset_fixed}px)`);
55              }
56              else {
57                // Between the top of the page and the threshold we calculate intermediate values
58                // to guarantee a smooth transition.
59                $search.css('margin-top', `-${currentScroll}px`);
60                $menu.css('margin-top', `${menuTopMargin + (scrollTopPixels - currentScroll)}px`);
61
62                if (currentScroll > 0) {
63                  const scrolledPercent = (scrollTopPixels - currentScroll) / scrollTopPixels;
64                  const offsetValue = menuHeightOffset_fixed + menuHeightOffset_diff * scrolledPercent;
65                  $menu.css('max-height', `calc(100% - ${offsetValue}px)`);
66                } else {
67                  $menu.css('max-height', `calc(100% - ${menuHeightOffset_default}px)`);
68                }
69              }
70            };
71
72            // The sidebar scroll event handler.
73            // Executed as the sidebar is scrolled as well as after the main scroll. This is needed
74            // because the main scroll can affect the scrollable area of the sidebar.
75            const handleSidebarScroll = () => {
76              const menuElement = $menu.get(0);
77              const menuScrollTop = $menu.scrollTop();
78              const menuScrollBottom = menuElement.scrollHeight - (menuScrollTop + menuElement.offsetHeight);
79
80              // As the navigation is scrolled we add a shadow to the top bar hanging over it.
81              if (menuScrollTop > 0) {
82                $search.addClass('fixed-and-scrolled');
83              } else {
84                $search.removeClass('fixed-and-scrolled');
85              }
86            };
87
88            $search.addClass('fixed');
89
90            $window.scroll(function() {
91              handleMainScroll(window.scrollY);
92              handleSidebarScroll();
93            });
94
95            $menu.scroll(function() {
96              handleSidebarScroll();
97            });
98
99            handleMainScroll(window.scrollY);
100            handleSidebarScroll();
101          } else {
102            // Entering the "mobile" state.
103
104            $window.unbind('scroll');
105            $menu.unbind('scroll');
106
107            $search.removeClass('fixed');
108
109            $search.css('margin-top', `0px`);
110            $menu.css('margin-top', `0px`);
111            $menu.css('max-height', 'initial');
112          }
113        };
114      })();
115
116      $(document).ready(() => {
117        // Initialize handlers for page scrolling and our custom sidebar.
118        const mediaQuery = window.matchMedia('only screen and (min-width: 769px)');
119
120        registerOnScrollEvent(mediaQuery);
121        mediaQuery.addListener(registerOnScrollEvent);
122      });
123