diff --git a/docs/_static/css/algolia.css b/docs/_static/css/algolia.css deleted file mode 100644 index d27f55e..0000000 --- a/docs/_static/css/algolia.css +++ /dev/null @@ -1,6 +0,0 @@ -.wy-nav-side { overflow: visible; } -.wy-side-scroll { overflow-x: inherit; } - -.algolia-autocomplete { - display: block !important; -} diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 7267e85..4300af9 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -592,6 +592,11 @@ html.writer-html5 .rst-content dl:not(.docutils) > dt, html.writer-html5 .rst-co background: var(--highlight-background-color); } +dl.simple dt { + border-bottom: 1px solid var(--hr-color); + background: none !important; +} + html.writer-html5 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt { border-left-color: var(--highlight-background-emph-color); background: var(--highlight-background-color); @@ -1905,8 +1910,3 @@ p + .classref-constant { padding: 3px 5px; } } - -/* Giscus */ -#godot-giscus { - margin-bottom: 1em; -} diff --git a/docs/_static/js/custom.js b/docs/_static/js/custom.js index bdc4a32..2d29ce2 100644 --- a/docs/_static/js/custom.js +++ b/docs/_static/js/custom.js @@ -1,431 +1,39 @@ -// Handle page scroll and adjust sidebar accordingly. - -// Each page has two scrolls: the main scroll, which is moving the content of the page; -// and the sidebar scroll, which is moving the navigation in the sidebar. -// We want the logo to gradually disappear as the main content is scrolled, giving -// more room to the navigation on the left. This means adjusting the height -// available to the navigation on the fly. There is also a banner below the navigation -// that must be dealt with simultaneously. -const registerOnScrollEvent = (function () { - // Configuration. - - // The number of pixels the user must scroll by before the logo is completely hidden. - const scrollTopPixels = 84; - // The target margin to be applied to the navigation bar when the logo is hidden. - const menuTopMargin = 70; - // The max-height offset when the logo is completely visible. - const menuHeightOffset_default = 162; - // The max-height offset when the logo is completely hidden. - const menuHeightOffset_fixed = 80; - // The distance between the two max-height offset values above; used for intermediate values. - const menuHeightOffset_diff = (menuHeightOffset_default - menuHeightOffset_fixed); - - // Media query handler. - return function (mediaQuery) { - // We only apply this logic to the "desktop" resolution (defined by a media query at the bottom). - // This handler is executed when the result of the query evaluation changes, which means that - // the page has moved between "desktop" and "mobile" states. - - // When entering the "desktop" state, we register scroll events and adjust elements on the page. - // When entering the "mobile" state, we clean up any registered events and restore elements on the page - // to their initial state. - - const $window = $(window); - const $sidebar = $('.wy-side-scroll'); - const $search = $sidebar.children('.wy-side-nav-search'); - const $menu = $sidebar.children('.wy-menu-vertical'); - const $ethical = $sidebar.children('.ethical-rtd'); - - // This padding is needed to correctly adjust the height of the scrollable area in the sidebar. - // It has to have the same height as the ethical block, if there is one. - let $menuPadding = $menu.children('.wy-menu-ethical-padding'); - if ($menuPadding.length == 0) { - $menuPadding = $('
'); - $menu.append($menuPadding); - } - - if (mediaQuery.matches) { - // Entering the "desktop" state. - - // The main scroll event handler. - // Executed as the page is scrolled and once immediately as the page enters this state. - const handleMainScroll = (currentScroll) => { - if (currentScroll >= scrollTopPixels) { - // After the page is scrolled below the threshold, we fix everything in place. - $search.css('margin-top', `-${scrollTopPixels}px`); - $menu.css('margin-top', `${menuTopMargin}px`); - $menu.css('max-height', `calc(100% - ${menuHeightOffset_fixed}px)`); - } else { - // Between the top of the page and the threshold we calculate intermediate values - // to guarantee a smooth transition. - $search.css('margin-top', `-${currentScroll}px`); - $menu.css('margin-top', `${menuTopMargin + (scrollTopPixels - currentScroll)}px`); - - if (currentScroll > 0) { - const scrolledPercent = (scrollTopPixels - currentScroll) / scrollTopPixels; - const offsetValue = menuHeightOffset_fixed + menuHeightOffset_diff * scrolledPercent; - $menu.css('max-height', `calc(100% - ${offsetValue}px)`); - } else { - $menu.css('max-height', `calc(100% - ${menuHeightOffset_default}px)`); - } - } - }; - - // The sidebar scroll event handler. - // Executed as the sidebar is scrolled as well as after the main scroll. This is needed - // because the main scroll can affect the scrollable area of the sidebar. - const handleSidebarScroll = () => { - const menuElement = $menu.get(0); - const menuScrollTop = $menu.scrollTop(); - const menuScrollBottom = menuElement.scrollHeight - (menuScrollTop + menuElement.offsetHeight); - - // As the navigation is scrolled we add a shadow to the top bar hanging over it. - if (menuScrollTop > 0) { - $search.addClass('fixed-and-scrolled'); - } else { - $search.removeClass('fixed-and-scrolled'); - } - - // Near the bottom we start moving the sidebar banner into view. - if (menuScrollBottom < ethicalOffsetBottom) { - $ethical.css('display', 'block'); - $ethical.css('margin-top', `-${ethicalOffsetBottom - menuScrollBottom}px`); - } else { - $ethical.css('display', 'none'); - $ethical.css('margin-top', '0px'); - } - }; - - $search.addClass('fixed'); - $ethical.addClass('fixed'); - - // Adjust the inner height of navigation so that the banner can be overlaid there later. - const ethicalOffsetBottom = $ethical.height() || 0; - if (ethicalOffsetBottom) { - $menuPadding.css('height', `${ethicalOffsetBottom}px`); - } else { - $menuPadding.css('height', `0px`); - } - - $window.scroll(function () { - handleMainScroll(window.scrollY); - handleSidebarScroll(); - }); - - $menu.scroll(function () { - handleSidebarScroll(); - }); - - handleMainScroll(window.scrollY); - handleSidebarScroll(); - } else { - // Entering the "mobile" state. - - $window.unbind('scroll'); - $menu.unbind('scroll'); - - $search.removeClass('fixed'); - $ethical.removeClass('fixed'); - - $search.css('margin-top', `0px`); - $menu.css('margin-top', `0px`); - $menu.css('max-height', 'initial'); - $menuPadding.css('height', `0px`); - $ethical.css('margin-top', '0px'); - $ethical.css('display', 'block'); - } - }; -})(); - -// Subscribe to DOM changes in the sidebar container, because there is a -// banner that gets added at a later point, that we might not catch otherwise. -const registerSidebarObserver = (function () { - return function (callback) { - const sidebarContainer = document.querySelector('.wy-side-scroll'); - - let sidebarEthical = null; - const registerEthicalObserver = () => { - if (sidebarEthical) { - // Do it only once. - return; - } - - sidebarEthical = sidebarContainer.querySelector('.ethical-rtd'); - if (!sidebarEthical) { - // Do it only after we have the element there. - return; - } - - // This observer watches over the ethical block in sidebar, and all of its subtree. - const ethicalObserverConfig = {childList: true, subtree: true}; - const ethicalObserverCallback = (mutationsList, observer) => { - for (let mutation of mutationsList) { - if (mutation.type !== 'childList') { - continue; - } - - callback(); - } - }; - - const ethicalObserver = new MutationObserver(ethicalObserverCallback); - ethicalObserver.observe(sidebarEthical, ethicalObserverConfig); - }; - registerEthicalObserver(); - - // This observer watches over direct children of the main sidebar container. - const observerConfig = {childList: true}; - const observerCallback = (mutationsList, observer) => { - for (let mutation of mutationsList) { - if (mutation.type !== 'childList') { - continue; - } - - callback(); - registerEthicalObserver(); - } - }; - - const observer = new MutationObserver(observerCallback); - observer.observe(sidebarContainer, observerConfig); - - // Default TOC tree has links that immediately navigate to the selected page. Our - // theme adds an extra button to fold and unfold the tree without navigating away. - // But that means that the buttons are added after the initial load, so we need to - // improvise to detect clicks on these buttons. - const scrollElement = document.querySelector('.wy-menu-vertical'); - const registerLinkHandler = (linkChildren) => { - linkChildren.forEach(it => { - if (it.nodeType === Node.ELEMENT_NODE && it.classList.contains('toctree-expand')) { - it.addEventListener('click', () => { - // Toggling a different item will close the currently opened one, - // which may shift the clicked item out of the view. We correct for that. - const menuItem = it.parentNode; - const baseScrollOffset = scrollElement.scrollTop + scrollElement.offsetTop; - const maxScrollOffset = baseScrollOffset + scrollElement.offsetHeight; - - if (menuItem.offsetTop < baseScrollOffset || menuItem.offsetTop > maxScrollOffset) { - menuItem.scrollIntoView(); - } - - callback(); - }); - } - }); - } - - const navigationSections = document.querySelectorAll('.wy-menu-vertical ul'); - navigationSections.forEach(it => { - if (it.previousSibling == null || typeof it.previousSibling === 'undefined' || it.previousSibling.tagName != 'A') { - return; - } - - const navigationLink = it.previousSibling; - registerLinkHandler(navigationLink.childNodes); - - const linkObserverConfig = {childList: true}; - const linkObserverCallback = (mutationsList, observer) => { - for (let mutation of mutationsList) { - registerLinkHandler(mutation.addedNodes); - } - }; - - const linkObserver = new MutationObserver(linkObserverCallback); - linkObserver.observe(navigationLink, linkObserverConfig); - }); - }; -})(); - -$(document).ready(() => { - // Remove the search match highlights from the page, and adjust the URL in the - // navigation history. - const url = new URL(location.href); - if (url.searchParams.has('highlight')) { - Documentation.hideSearchWords(); - } - - window.addEventListener('keydown', function (event) { - if (event.key === '/') { - var searchField = document.querySelector('#rtd-search-form input[type=text]'); - if (document.activeElement !== searchField) { - searchField.focus(); - searchField.select(); - event.preventDefault(); - } - } - }); - - // Initialize handlers for page scrolling and our custom sidebar. - const mediaQuery = window.matchMedia('only screen and (min-width: 769px)'); - - registerOnScrollEvent(mediaQuery); - mediaQuery.addListener(registerOnScrollEvent); - - registerSidebarObserver(() => { - registerOnScrollEvent(mediaQuery); - }); - - // Add line-break suggestions to the sidebar navigation items in the class reference section. - // - // Some class reference pages have very long PascalCase names, such as - // VisualShaderNodeCurveXYZTexture - // Those create issues for our layout, as we can neither make them wrap with CSS without - // breaking normal article titles, nor is it good to have them overflow their containers. - // So we use a tag to insert mid-word suggestions for appropriate splits, so the browser - // knows that it's okay to split it like - // Visual Shader Node Curve XYZTexture - // and add a new line at an opportune moment. - const classReferenceLinks = document.querySelectorAll('.wy-menu-vertical > ul:last-of-type .reference.internal'); - for (const linkItem of classReferenceLinks) { - let textNode = null; - linkItem.childNodes.forEach(it => { - if (it.nodeType === Node.TEXT_NODE) { - // If this is a text node and if it needs to be updated, store a reference. - let text = it.textContent; - if (!(text.includes(" ") || text.length < 10)) { - textNode = it; - } - } - }); - - if (textNode != null) { - let text = textNode.textContent; - // Add suggested line-breaks and replace the original text. - // The regex looks for a lowercase letter followed by a number or an uppercase - // letter. We avoid splitting at the last character in the name, though. - text = text.replace(/([a-z])([A-Z0-9](?!$))/gm, '$1$2'); - - linkItem.removeChild(textNode); - linkItem.insertAdjacentHTML('beforeend', text); - } - } - - // Change indentation from spaces to tabs for codeblocks. - const codeBlocks = document.querySelectorAll('.rst-content div[class^="highlight"] pre'); - for (const codeBlock of codeBlocks) { - const classList = codeBlock.parentNode.parentNode.classList; - if (!classList.contains('highlight-gdscript') && !classList.contains('highlight-cpp')) { - // Only change indentation for GDScript and C++. - continue; - } - let html = codeBlock.innerHTML.replace(/^()?( {4})/gm, '\t'); - let html_old = ""; - while (html != html_old) { - html_old = html; - html = html.replace(/\t( {4})/gm, '\t\t') - } - codeBlock.innerHTML = html; - } - - // See `godot_is_latest` in conf.py - const isLatest = document.querySelector('meta[name=doc_is_latest]').content.toLowerCase() === 'true'; - if (isLatest) { - // Add a compatibility notice using JavaScript so it doesn't end up in the - // automatically generated `meta description` tag. - - const baseUrl = [location.protocol, '//', location.host, location.pathname].join(''); - // These lines only work as expected in the production environment, can't test this locally. - const fallbackUrl = baseUrl.replace('/latest/', '/stable/'); - const homeUrl = baseUrl.split('/latest/')[0] + '/stable/'; - const searchUrl = homeUrl + 'search.html?q='; - - const noticeLink = document.querySelector('.latest-notice-link'); - - // Insert a placeholder to display as we're making a request. - noticeLink.innerHTML = ` - Checking the stable version - of the documentation... - `; +document.addEventListener("DOMContentLoaded", () => { + const menuHeaders = document.querySelectorAll('.wy-menu-vertical .caption'); + let hasCurrent = false; - // Make a HEAD request to the possible stable URL to check if the page exists. - fetch(fallbackUrl, {method: 'HEAD'}) - .then((res) => { - // We only check the HTTP status, which should tell us if the link is valid or not. - if (res.status === 200) { - noticeLink.innerHTML = ` - See the stable version - of this documentation page instead. - `; - } else { - // Err, just to fallthrough to catch. - throw Error('Bad request'); - } - }) - .catch((err) => { - let message = ` - This page does not exist in the stable version - of the documentation. - `; + menuHeaders.forEach(header => { + const connectedMenu = header.nextElementSibling; // The dropdown menu (e.g., `ul`) - // Also suggest a search query using the page's title. It should work with translations as well. - // Note that we can't use the title tag as it has a permanent suffix. OG title doesn't, though. - const titleMeta = document.querySelector('meta[property="og:title"]'); - if (typeof titleMeta !== 'undefined') { - const pageTitle = titleMeta.getAttribute('content'); - message += ` - You can try searching for "${pageTitle}" instead. - `; - } + // Toggle dropdown visibility on click + header.addEventListener('click', () => { + const isActive = connectedMenu.classList.contains('active'); - noticeLink.innerHTML = message; + // Close all dropdowns + menuHeaders.forEach(otherHeader => { + const otherMenu = otherHeader.nextElementSibling; + otherMenu.classList.remove('active'); + otherHeader.classList.remove('active'); }); - } - // Load instant.page to prefetch pages upon hovering. This makes navigation feel - // snappier. The script is dynamically appended as Read the Docs doesn't have - // a way to add scripts with a "module" attribute. - const instantPageScript = document.createElement('script'); - instantPageScript.toggleAttribute('module'); - /*! instant.page v5.1.0 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */ - instantPageScript.innerText = 'let t,e;const n=new Set,o=document.createElement("link"),i=o.relList&&o.relList.supports&&o.relList.supports("prefetch")&&window.IntersectionObserver&&"isIntersecting"in IntersectionObserverEntry.prototype,s="instantAllowQueryString"in document.body.dataset,a="instantAllowExternalLinks"in document.body.dataset,r="instantWhitelist"in document.body.dataset,c="instantMousedownShortcut"in document.body.dataset,d=1111;let l=65,u=!1,f=!1,m=!1;if("instantIntensity"in document.body.dataset){const t=document.body.dataset.instantIntensity;if("mousedown"==t.substr(0,"mousedown".length))u=!0,"mousedown-only"==t&&(f=!0);else if("viewport"==t.substr(0,"viewport".length))navigator.connection&&(navigator.connection.saveData||navigator.connection.effectiveType&&navigator.connection.effectiveType.includes("2g"))||("viewport"==t?document.documentElement.clientWidth*document.documentElement.clientHeight<45e4&&(m=!0):"viewport-all"==t&&(m=!0));else{const e=parseInt(t);isNaN(e)||(l=e)}}if(i){const n={capture:!0,passive:!0};if(f||document.addEventListener("touchstart",function(t){e=performance.now();const n=t.target.closest("a");if(!h(n))return;v(n.href)},n),u?c||document.addEventListener("mousedown",function(t){const e=t.target.closest("a");if(!h(e))return;v(e.href)},n):document.addEventListener("mouseover",function(n){if(performance.now()-e{v(o.href),t=void 0},l)},n),c&&document.addEventListener("mousedown",function(t){if(performance.now()-e1||t.metaKey||t.ctrlKey)return;if(!n)return;n.addEventListener("click",function(t){1337!=t.detail&&t.preventDefault()},{capture:!0,passive:!1,once:!0});const o=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!1,detail:1337});n.dispatchEvent(o)},n),m){let t;(t=window.requestIdleCallback?t=>{requestIdleCallback(t,{timeout:1500})}:t=>{t()})(()=>{const t=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){const n=e.target;t.unobserve(n),v(n.href)}})});document.querySelectorAll("a").forEach(e=>{h(e)&&t.observe(e)})})}}function p(e){e.relatedTarget&&e.target.closest("a")==e.relatedTarget.closest("a")||t&&(clearTimeout(t),t=void 0)}function h(t){if(t&&t.href&&(!r||"instant"in t.dataset)&&(a||t.origin==location.origin||"instant"in t.dataset)&&["http:","https:"].includes(t.protocol)&&("http:"!=t.protocol||"https:"!=location.protocol)&&(s||!t.search||"instant"in t.dataset)&&!(t.hash&&t.pathname+t.search==location.pathname+location.search||"noInstant"in t.dataset))return!0}function v(t){if(n.has(t))return;const e=document.createElement("link");e.rel="prefetch",e.href=t,document.head.appendChild(e),n.add(t)}'; - document.head.appendChild(instantPageScript); - - // Make sections in the sidebar togglable. - let hasCurrent = false; - let menuHeaders = document.querySelectorAll('.wy-menu-vertical .caption[role=heading]'); - menuHeaders.forEach(it => { - let connectedMenu = it.nextElementSibling; - - // Enable toggling. - it.addEventListener('click', () => { - if (connectedMenu.classList.contains('active')) { - connectedMenu.classList.remove('active'); - it.classList.remove('active'); - } else { + // Open the clicked dropdown if it was closed + if (!isActive) { connectedMenu.classList.add('active'); - it.classList.add('active'); + header.classList.add('active'); } + }); - // Hide other sections. - menuHeaders.forEach(ot => { - if (ot !== it && ot.classList.contains('active')) { - ot.nextElementSibling.classList.remove('active'); - ot.classList.remove('active'); - } - }); - - registerOnScrollEvent(mediaQuery); - }, true); - - // Set the default state, expand our current section. - if (connectedMenu.classList.contains('current')) { + // Automatically expand the dropdown if it contains the "current" page + if (connectedMenu.querySelector('.current')) { connectedMenu.classList.add('active'); - it.classList.add('active'); - + header.classList.add('active'); hasCurrent = true; } }); - // Unfold the first (general information) section on the home page. + // If no dropdown contains the "current" page, expand the first section by default if (!hasCurrent && menuHeaders.length > 0) { menuHeaders[0].classList.add('active'); menuHeaders[0].nextElementSibling.classList.add('active'); - - registerOnScrollEvent(mediaQuery); } }); - -// Override the default implementation from doctools.js to avoid this behavior. -Documentation.highlightSearchWords = function () { - // Nope. -} \ No newline at end of file diff --git a/docs/_static/rect.png b/docs/_static/rect.png new file mode 100644 index 0000000..115a1c4 Binary files /dev/null and b/docs/_static/rect.png differ diff --git a/docs/conf.py b/docs/conf.py index b46ae46..d62754d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,15 +1,15 @@ import os import subprocess -breathe_projects = {'KrakenEngine': 'xml'} -breathe_default_project = 'KrakenEngine' +breathe_projects = {"KrakenEngine": "xml"} +breathe_default_project = "KrakenEngine" needs_sphinx = "7.1" extensions = [ - 'breathe', - 'sphinx.ext.autosectionlabel', - 'notfound.extension', + "breathe", + "sphinx.ext.autosectionlabel", + "notfound.extension", ] notfound_context = { @@ -35,22 +35,22 @@ on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: - subprocess.call('doxygen', shell=True) + subprocess.call("doxygen", shell=True) else: notfound_urls_prefix = '' templates_path = ["_templates"] -source_suffix = '.rst' +source_suffix = ".rst" source_encoding = "utf-8-sig" -master_doc = 'index' +master_doc = "index" -project = 'Kraken Engine' -copyright = '2024, Derrick Martinez' -author = 'Derrick Martinez' +project = "Kraken Engine" +copyright = "2024, Derrick Martinez" +author = "Derrick Martinez" -version = os.getenv("READTHEDOCS_VERSION", "0.0.3") +version = os.getenv("READTHEDOCS_VERSION", "0.0.6") release = version exclude_patterns = ["_build"] @@ -66,7 +66,6 @@ html_theme_options = { "logo_only": True, "collapse_navigation": False, - "display_version": False, } html_title = f"Kraken Engine ({version}) documentation in English" @@ -75,8 +74,8 @@ "display_github": True, # Integrate GitHub "github_user": "durkisneer1", # Username "github_repo": "Kraken-Engine", # Repo name - "github_version": "0.0.3", # Version - "conf_py_path": "/", # Path in the checkout to the docs root + "github_version": "main", # Version + "conf_py_path": "/docs/", # Path in the checkout to the docs root "kraken_docs_title": html_title, "kraken_docs_basepath": "https://kraken-engine.readthedocs.io/", "kraken_docs_suffix": ".html", @@ -84,24 +83,16 @@ "kraken_canonical_version": "stable", # Set this to `True` when in the `latest` branch to clearly indicate to the reader # that they are not reading the `stable` documentation. - "kraken_is_latest": False, - "kraken_version": "0.0.3", + "kraken_is_latest": True, + "kraken_version": "0.0.6", # Enables a banner that displays the up-to-date status of each article. "kraken_show_article_status": True, } html_logo = "_static/kraken-engine-banner.png" -html_static_path = ['_static'] - -html_css_files = [ - 'css/algolia.css', - 'https://cdn.jsdelivr.net/npm/@docsearch/css@3', - "css/custom.css", -] - -html_js_files = [ - "js/custom.js", -] +html_static_path = ["_static"] +html_css_files = ["css/custom.css"] +html_js_files = ["js/custom.js"] file_insertion_enabled = False @@ -110,4 +101,4 @@ gettext_compact = False -epub_tocscope = 'includehidden' +epub_tocscope = "includehidden" diff --git a/docs/contributing/guidelines.rst b/docs/contributing/guidelines.rst new file mode 100644 index 0000000..148f3eb --- /dev/null +++ b/docs/contributing/guidelines.rst @@ -0,0 +1,70 @@ +Coding Guidelines +================= + +This document outlines the coding guidelines for Kraken Engine. +These guidelines are intended to ensure that Kraken stays in its best shape and documentation is consistent and easy to read. + +General Principles +------------------ + +- **Consistency**: Follow the style of the surrounding code. This is elaborated in the next section. +- **Clarity**: Write code that's easy to read and understand. This means using descriptive names for variables, functions, and classes. +- **Documentation**: Write clear, concise, and useful comments. +- **Testing**: Write tests for your code (you may ask an experienced contributor to assist you). +- **Error Handling**: Check for errors and handle them appropriately. + + +Style Guidelines +---------------- + +Kraken Engine uses a consistent code style defined by the ``.clang-format`` file in the root directory. Additionally, the ``.pre-commit-config.yaml`` file automatically runs clang-format on staged files before committing, ensuring proper formatting. + +However, the formatter doesn't catch everything. Please follow these additional guidelines: + +- **Naming Conventions**: + - Classes, Structs, and Enums: ``PascalCase`` + - Functions and Variables: ``camelCase`` + - Constants: ``ALL_CAPS`` + - Header Files: ``PascalCase.hpp`` + - Source Files: ``snake_case.cpp`` +- **Header Includes**: Only include headers that are strictly necessary in a given file. Avoid including general-purpose headers (e.g., ````) in header files unless they are directly needed there. Instead, include them in source files where applicable. This practice reduces unnecessary dependencies and improves compile times. +- **Comments**: Focus comments on the *why* behind your code, not the *what*. Clear and descriptive function and variable names should make the code's intent self-explanatory. + +Documentation Guidelines +------------------------ + +Kraken Engine uses **Sphinx** and **Doxygen** for generating documentation. +Follow these guidelines to maintain consistency and clarity: + +Writing Docstrings +~~~~~~~~~~~~~~~~~~ + +When writing docstrings, adhere to the **Doxygen** format. Here's an example: + +.. code-block:: cpp + + /** + * @brief Adds two numbers. + * + * @param a The left-hand side operand. + * @param b The right-hand side operand. + * + * @return The sum of a and b. + */ + int addNums(int a, int b); + +For more details on Doxygen formatting, refer to the `official Doxygen documentation `_. + +Writing Documentation +~~~~~~~~~~~~~~~~~~~~~ + +Use **reStructuredText (RST)** format for creating and editing documentation files. + +- If you add a new class, function, or namespace to the codebase, create a corresponding ``.rst`` file in the appropriate directory under ``docs/``. +- Include the following details in the ``.rst`` file: + - A clear description of the class, function, or namespace. + - Example usage code (if applicable). + - Doxygen's generated documentation. + +For guidance on RST syntax, check the `official Sphinx documentation `_. +You can also review existing ``.rst`` files in the repository as examples. diff --git a/docs/contributing/how_to_contribute.rst b/docs/contributing/how_to_contribute.rst index ffcf026..6b0a08e 100644 --- a/docs/contributing/how_to_contribute.rst +++ b/docs/contributing/how_to_contribute.rst @@ -1,7 +1,71 @@ How To Contribute ================= -This page is currently under construction. Please check back later for updates. +Welcome! +Your contributions help make Kraken Engine better for everyone, +whether it's fixing a bug, adding a feature, or improving the documentation. -.. image:: ../_static/under_construction.gif - :alt: Page Under Construction \ No newline at end of file +Prerequisites +------------- + +Before you begin, ensure you have: + +- A GitHub account +- Git installed locally +- A C/C++ compiler (e.g., GCC, Clang, MSVC) +- Python 3.9 or later +- Doxygen +- Familiarity with our :doc:`guidelines` + +Setting Up +---------- + +The Kraken Engine repository uses a `dev` branch (e.g., ``0.0.6dev``) for upcoming releases. +Base your work on this branch. + +1. Fork and clone the current `dev` branch. +2. Run ``pip install -r requirements.txt`` to install dependencies. + +Compiling +--------- + +To control which of the following targets you want to build, change the options in ``meson_options.txt``. +For example, both ``build_example`` and ``build_tests`` set to ``false`` builds only the library. +Then, run either of the following commands: + +Library Only +~~~~~~~~~~~~ + +.. code-block:: bash + + meson setup build + meson compile kraken -C build + +Library + Example Demo +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + meson setup build + meson compile krakenapp -C build + +Library + Test Suite +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + meson setup build + meson compile krakentests -C build + + +Making Changes +-------------- + +*Before making changes to Kraken, please read the* :doc:`guidelines`. + +1. Commit and push your changes to your fork. +2. Submit a pull request to the `dev` branch of the Kraken Engine repository. +3. Address any feedback from maintainers. +4. Once approved, your changes will be merged, and you will be credited in the changelog. + +Thank you for contributing to Kraken Engine! diff --git a/docs/contributing/testing.rst b/docs/contributing/testing.rst new file mode 100644 index 0000000..2948d05 --- /dev/null +++ b/docs/contributing/testing.rst @@ -0,0 +1,37 @@ +Testing Your Contributions +========================== + +Before submitting a pull request, you should run tests and preview the documentation to ensure that your changes are correct. + +Testing Code +---------------- + +If you haven't already, read through building the *test suite* in the :doc:`how_to_contribute` section. + +Tests are written using the Google Test framework and can be found in the ``tests`` directory. +After making changes or adding new features, run the test suite to ensure that your changes do not break existing or new functionality. + +Previewing Documentation +------------------------ + +1. Install the required dependencies via pip: + +.. code-block:: bash + + pip install -r docs/requirements.txt + +2. Generate the documentation with Doxygen: + +.. code-block:: bash + + cd docs + doxygen + +1. Build the documentation: + +.. code-block:: bash + + make -C docs html + +You will find the generated documentation in the ``docs/_build/html`` directory. +If everything looks as intended, you can submit your pull request. diff --git a/docs/getting_started/create_window.rst b/docs/getting_started/create_window.rst index 36b3e83..0b19486 100644 --- a/docs/getting_started/create_window.rst +++ b/docs/getting_started/create_window.rst @@ -2,30 +2,51 @@ Creating a Window ================= After following the :doc:`installation` guide, you are ready for your first Kraken Engine program. +The following code creates a window and keeps it open until the user closes it. .. code-block:: c++ :linenos: #include - int main() - { + int main() { kn::window::init({800, 600}); - kn::time::Clock clock; + kn::Event event; - bool done = false; - while (!done) - { - clock.tick(); - - for (const auto &event : kn::window::getEvents()) - if (event.type == kn::QUIT) - done = true; - - kn::window::clear(); + while (kn::window::isOpen()) { + while (kn::window::pollEvent(event)) { + // handle events + } kn::window::flip(); } - kn::window::quit(); + return EXIT_SUCCESS; } + +Explanation +----------- + +This simple program serves as a basic skeleton for applications built with the Kraken Engine. +Let's break down the key components that make this program function: + +* Initializing the Window + ``kn::window::init({800, 600})`` sets up a rendering window with a resolution of 800x600 pixels. + This function prepares the underlying graphical context, ensuring the application is ready to display content. + +* Event Handling + Before entering the main game loop, an ``Event`` union is instantiated. + This union acts as a container for user inputs and system events. + The call to ``kn::window::pollEvent(event)`` continuously monitors for user interactions or system-generated signals, populating the event union with the details of the most recent occurrence. + +* Closing the Application + By default, Kraken Engine applications close when the user clicks the window’s close button. + Upon detecting this event internally, the call to ``kn::window::close()`` ends the game loop. + +* Updating the Display + ``kn::window::flip()`` acts as the final piece in the game loop, swapping the back buffer to the front. + This step ensures that any rendering operations performed during the loop are visually updated on the screen. + Think of it as flipping the pages of a book—ensuring smooth, sequential updates for the user. + +This template establishes a foundation for more advanced game logic and rendering. +In-depth tutorials on the next page cover the more intricate aspects of the Kraken Engine. diff --git a/docs/getting_started/installation.rst b/docs/getting_started/installation.rst index 23b4473..b5aae2e 100644 --- a/docs/getting_started/installation.rst +++ b/docs/getting_started/installation.rst @@ -9,9 +9,13 @@ Before starting, ensure you have the Meson build system and a C++ compiler insta mkdir subprojects -2. Run ``meson wrap install kraken-engine`` to install the latest version of Kraken Engine. +2. Use the following command to install the latest version of Kraken Engine: -3. Include the Kraken Engine dependency in your Meson build file. +.. code-block:: bash + + meson wrap install kraken-engine + +3. Include the Kraken Engine dependency in your Meson build file (usually ``meson.build``). .. code-block:: python @@ -19,4 +23,21 @@ Before starting, ensure you have the Meson build system and a C++ compiler insta kraken_dep = dependency('kraken-engine') executable('MyProject', 'main.cpp', dependencies: kraken_dep) -4. Continue with the typical Meson build and compile process. +4. Build and compile your project using Meson: + +.. code-block:: bash + + meson setup build + cd build + meson compile + +You can also use the ``-j`` flag to specify the number of threads to use to speed up compilation (e.g., ``meson compile -j4``). + +Updating +-------- + +To update Kraken Engine to the latest version, use the following command: + +.. code-block:: bash + + meson wrap update kraken-engine diff --git a/docs/index.rst b/docs/index.rst index 20b0cd2..61ae93c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,35 +1,40 @@ Kraken Engine Docs ================== -The **Kraken Engine** is a powerful extension to *SDL2*, designed to streamline your -game development process. It provides a suite of easy-to-use features such as texture -caching and collision logic, allowing you to focus on creating immersive experiences -for your players. +The **Kraken Engine** is a robust extension to *SDL2*, designed to strike a balance between high- and low-level control in game development. +It supports input handling, audio management, and extra utilities like tile maps and animation controllers. About ----- -This documentation is a comprehensive guide to the Kraken Engine, covering everything -from installation to advanced features. It's intended for both beginners and -experienced developers. +This documentation provides a comprehensive guide to the Kraken Engine, catering to developers with a foundational understanding of C++. +As the engine evolves, it will also become increasingly accessible to beginners, with a friendlier API and enhanced documentation. Future of Kraken Engine ----------------------- -Future plans for the Kraken Engine include the implementation of essential mathematical -functions like pathfinding, ray casting, and a built-in physics engine, further enhancing -its capabilities and versatility. +Planned features for the Kraken Engine include implementing essential mathematical tools like pathfinding, ray casting, and a built-in physics engine. +These additions will further expand the engine’s capabilities and make it a one-stop solution for 2D game development. + +Advanced Features +----------------- + +The Kraken Engine stands out with its: + +- **Animation Controller Class**: A utility for managing sprite sheet animations efficiently. +- **Tile Map Class**: Simplifying the process of loading, rendering, and managing tile maps. Community --------- -Join our `discord `_ to get help, share your projects, and contribute to the Kraken Engine! +Join our `Discord `_ to connect with fellow developers, share your projects, and contribute to the Kraken Engine. -We look forward to seeing the incredible games you create with the Kraken Engine. +We’re excited to see how you push the boundaries of creativity with the Kraken Engine! .. toctree:: :hidden: :maxdepth: 1 + :caption: Getting Started getting_started/installation getting_started/create_window @@ -37,17 +42,36 @@ We look forward to seeing the incredible games you create with the Kraken Engine .. toctree:: :hidden: :maxdepth: 1 + :caption: Manual tutorials/index .. toctree:: :hidden: :maxdepth: 1 + :caption: Contributing contributing/how_to_contribute + contributing/guidelines + contributing/testing .. toctree:: :hidden: :maxdepth: 1 - - reference/index \ No newline at end of file + :caption: API Reference + + reference/animation_controller + reference/font + reference/rect + reference/sound + reference/texture + reference/tile_map + reference/constants + reference/controller + reference/draw + reference/key + reference/math + reference/mouse + reference/music + reference/time + reference/window \ No newline at end of file diff --git a/docs/reference/animation_controller.rst b/docs/reference/animation_controller.rst index 6049823..9d824a0 100644 --- a/docs/reference/animation_controller.rst +++ b/docs/reference/animation_controller.rst @@ -11,21 +11,24 @@ What the AnimationController class does **NOT** do is provide a draw function. This design decision is to give users the flexibility to draw the current frame in a desired position and size. Usage -------------- +----- .. code-block:: cpp - // Create an AnimationController with a frame rate of 10 frames per second. - kn::AnimationController animController(10); + // Instantiate an AnimationController. + kn::AnimationController animController; // Load animations from sprite sheets where every frame is 16x16 pixels. - animController.addAnim("assets/idle.png", "idle", 16, 16); - animController.addAnim("assets/walk.png", "walk", 16, 16); + animController.addAnim("idle", "assets/idle.png", {16, 16}, 5); // 5fps + animController.addAnim("walk", "assets/walk.png", {16, 16}, 10); // 10fps // Since the current animation set is the last one added, // we can switch to the first animation by name. animController.setAnim("idle"); + // Change the animation controller's playback speed. + animController.setPlaybackSpeed(-1.5f); // Reverse playback at 1.5x speed. + // Using AnimationController::nextFrame is simple. // It returns the current frame texture, srcRect, and advances the animation. const Frame frame = animController.nextFrame(deltaTime); @@ -33,5 +36,16 @@ Usage // Draw the current frame texture at position (50, 50) and size (16, 16). kn::window::blit(*frame.tex, {50, 50, 16, 16}, frame.rect); +Members +------- + +.. doxygenstruct:: kn::Frame + :members: + :undoc-members: + +.. doxygenstruct:: kn::Animation + :members: + :undoc-members: + .. doxygenclass:: kn::AnimationController :members: diff --git a/docs/reference/controller.rst b/docs/reference/controller.rst new file mode 100644 index 0000000..418053f --- /dev/null +++ b/docs/reference/controller.rst @@ -0,0 +1,40 @@ +controller +========== + +Description +----------- + +The **controller** namespace contains functions to handle input from a game controller. + +.. note:: The default dead zone for the joysticks is 0.1f. + +Usage +----- + +.. code-block:: cpp + + // Get the vector of the left joystick + kn::Vec2 leftJoystick = kn::controller::getLeftJoystick(); + + // Change the joysticks' dead zones + kn::controller::setDeadZone(0.2f); + + // Get how far the right trigger is pressed + if (kn::controller::getRightTrigger() > 0.5f) { + // Do something + } + +Functions +--------- + +.. doxygenfunction:: kn::controller::getLeftJoystick + +.. doxygenfunction:: kn::controller::getRightJoystick + +.. doxygenfunction:: kn::controller::getLeftTrigger + +.. doxygenfunction:: kn::controller::getRightTrigger + +.. doxygenfunction:: kn::controller::setDeadZone + +.. doxygenfunction:: kn::controller::getDeadZone diff --git a/docs/reference/font.rst b/docs/reference/font.rst index 3ad2e13..b21ddf2 100644 --- a/docs/reference/font.rst +++ b/docs/reference/font.rst @@ -20,5 +20,8 @@ Usage // Draw the text texture at position (50, 50). kn::window::blit(text, {50, 50}); +Members +------- + .. doxygenclass:: kn::Font :members: \ No newline at end of file diff --git a/docs/reference/index.rst b/docs/reference/index.rst deleted file mode 100644 index bb07367..0000000 --- a/docs/reference/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -============= -API Reference -============= - -.. toctree:: - :maxdepth: 1 - - animation_controller.rst - font.rst - rect.rst - sound.rst - texture.rst - tile_map.rst - constants.rst - draw.rst - input.rst - math.rst - music.rst - time.rst - window.rst \ No newline at end of file diff --git a/docs/reference/input.rst b/docs/reference/input.rst deleted file mode 100644 index c42560a..0000000 --- a/docs/reference/input.rst +++ /dev/null @@ -1,40 +0,0 @@ -input -===== - -Description ------------ - -The **input** namespace contains functions to get input from the keyboard and mouse. -:code:`kn::input::getVector` is going to be your best friend when you need to get 8-directional input. -This function is planned to also support controller joystick input in the future. - -Usage ------ - -.. code-block:: cpp - - // Check if the left mouse button is pressed. - if (kn::input::isMouseButtonPressed(kn::BUTTON_LEFT)) { - // Handle left mouse button press. - } - - // Check if the 'W' key is pressed. - if (kn::input::isKeyPressed(kn::S_w)) { - // Handle 'W' key press. - } - - // Using the getVector function to get 8-directional input. - std::vector left = {kn::S_a, kn::S_LEFT}; - std::vector right = {kn::S_d, kn::S_RIGHT}; - std::vector up = {kn::S_w, kn::S_UP}; - std::vector down = {kn::S_s, kn::S_DOWN}; - - kn::math::Vec2 inputDir = kn::input::getVector(left, right, up, down); - -.. doxygenfunction:: kn::input::getMousePos - -.. doxygenfunction:: kn::input::isMouseButtonPressed - -.. doxygenfunction:: kn::input::isKeyPressed - -.. doxygenfunction:: kn::input::getVector \ No newline at end of file diff --git a/docs/reference/key.rst b/docs/reference/key.rst new file mode 100644 index 0000000..cc09ff9 --- /dev/null +++ b/docs/reference/key.rst @@ -0,0 +1,28 @@ +key +=== + +Description +----------- + +The **key** namespace contains functions to handle input from the keyboard. +The two types of key codes are `scancodes` and `keycodes`. +Scancodes are the physical location of the key on the keyboard. +Keycodes are the character that the key represents. +For example, 'W' has a scancode of ``kn::S_w`` and a keycode of ``kn::K_w``. + +.. note:: A function similar to the removed ``kn::input::getVector`` will eventually be added to this namespace. + +Usage +----- + +.. code-block:: cpp + + // Check if the 'W' key is pressed. + if (kn::key::isPressed(kn::S_w)) { + // Handle 'W' key press. + } + +Functions +--------- + +.. doxygenfunction:: kn::key::isPressed \ No newline at end of file diff --git a/docs/reference/mouse.rst b/docs/reference/mouse.rst new file mode 100644 index 0000000..c51ce2d --- /dev/null +++ b/docs/reference/mouse.rst @@ -0,0 +1,28 @@ +mouse +===== + +Description +----------- + +The **mouse** namespace contains functions to handle input from the mouse. + +Usage +----- + +.. code-block:: cpp + + // Check if the left mouse button is pressed. + if (kn::mouse::isPressed(kn::BUTTON_LEFT)) { + // Handle left mouse button press. + } + + // Get the mouse position and draw a circle at that position. + kn::math::Vec2 mousePos = kn::mouse::getPos(); + kn::draw::circle(mousePos, 10, {255, 0, 0}); + +Functions +--------- + +.. doxygenfunction:: kn::mouse::getPos + +.. doxygenfunction:: kn::mouse::isPressed \ No newline at end of file diff --git a/docs/reference/music.rst b/docs/reference/music.rst index c8ab7e0..3f7ed3e 100644 --- a/docs/reference/music.rst +++ b/docs/reference/music.rst @@ -28,6 +28,9 @@ Usage // Fade out the music over 2 seconds. kn::music::fadeOut(2000); +Functions +--------- + .. doxygenfunction:: kn::music::load .. doxygenfunction:: kn::music::unload diff --git a/docs/reference/rect.rst b/docs/reference/rect.rst index da7047e..348557a 100644 --- a/docs/reference/rect.rst +++ b/docs/reference/rect.rst @@ -1,6 +1,42 @@ Rect ==== +Description +----------- + +The **Rect** class represents a rectangle in 2D space. It is defined by its top-left corner and its size. +Its most common uses are source and destination rectangles in blitting operations, as well as collision detection. + +It may be undocumented, but there are getter and setter methods for all 9 positions in the rectangle, as shown below: + +.. image:: ../_static/rect.png + :alt: The 9 positions of a Rect + :width: 500px + +Usage +----- + +.. code-block:: cpp + + // Instantiate a Rect. + kn::Rect rectA{50, 50, 16, 16}; + + // Instantiate another Rect. + kn::Rect rectB{100, 100, 12, 12}; + + // Check if they collide. + if (rectA.collideRect(rectB)) { + // handle collision + } + + // Move the top left of rectB to the bottom right of rectA + rectB.setTopLeft(rectA.getBottomRight()); + + // Clamp rectA inside rectB + rectA.clamp(rectB); + +Members +------- + .. doxygenstruct:: kn::Rect :members: - :undoc-members: \ No newline at end of file diff --git a/docs/reference/sound.rst b/docs/reference/sound.rst index f57ef6f..306b7db 100644 --- a/docs/reference/sound.rst +++ b/docs/reference/sound.rst @@ -25,5 +25,8 @@ Usage // Play the sound 3 times after it fades in over 2 seconds. sound.play(2, -1, 2000); +Members +------- + .. doxygenclass:: kn::Sound :members: diff --git a/docs/reference/texture.rst b/docs/reference/texture.rst index 7b207fb..95a8904 100644 --- a/docs/reference/texture.rst +++ b/docs/reference/texture.rst @@ -29,6 +29,8 @@ Usage kn::window::blit(imageTexture, {50, 50}); kn::window::blit(colorTexture, {100, 100}); +Members +------- .. doxygenclass:: kn::Texture :members: \ No newline at end of file diff --git a/docs/reference/tile_map.rst b/docs/reference/tile_map.rst index bac1474..88d1791 100644 --- a/docs/reference/tile_map.rst +++ b/docs/reference/tile_map.rst @@ -41,6 +41,9 @@ Usage tileMap.drawMap(); tileMap.drawLayer("walls"); +Members +------- + .. doxygenstruct:: kn::Tile :members: :undoc-members: diff --git a/docs/reference/time.rst b/docs/reference/time.rst index fe0ecc1..b695103 100644 --- a/docs/reference/time.rst +++ b/docs/reference/time.rst @@ -21,7 +21,13 @@ Usage // Get the time since Kraken was initialized. double elapsedTime = kn::time::getTicks(); +Members +------- + .. doxygenclass:: kn::time::Clock :members: +Functions +--------- + .. doxygenfunction:: kn::time::getTicks diff --git a/docs/reference/window.rst b/docs/reference/window.rst index c6e90be..9632772 100644 --- a/docs/reference/window.rst +++ b/docs/reference/window.rst @@ -1,4 +1,6 @@ window ====== +.. doxygenvariable:: kn::camera + .. doxygennamespace:: kn::window \ No newline at end of file diff --git a/example/main.cpp b/example/main.cpp index 31fd963..1daf011 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -12,15 +12,16 @@ int main() Player player(tileMap); - bool done = false; - while (!done) + kn::Event event; + while (kn::window::isOpen()) { - const double dt = clock.tick(); + const double dt = clock.tick() / 1000.0; - for (const auto& event : kn::window::getEvents()) - if (event.type == kn::QUIT || - (event.type == kn::KEYDOWN && event.key.keysym.sym == kn::K_ESCAPE)) - done = true; + while (kn::window::pollEvent(event)) + { + if (event.type == kn::KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) + kn::window::close(); + } kn::window::clear({21, 18, 37}); diff --git a/example/src/player.cpp b/example/src/player.cpp index e868b59..c4e6686 100644 --- a/example/src/player.cpp +++ b/example/src/player.cpp @@ -4,10 +4,10 @@ #define AxisY 1 Player::Player(const kn::TileMap& tileMap) - : animController(5), rect(48, 120, 13, 16), collisionLayer(tileMap.getLayer("Wall")) + : rect(48, 120, 13, 16), collisionLayer(tileMap.getLayer("Wall")) { - animController.addAnim("../example/assets/player_idle.png", "idle", 13, 16); - animController.addAnim("../example/assets/player_walk.png", "walk", 13, 16); + animController.addAnim("idle", "../example/assets/player_idle.png", {13, 16}, 5); + animController.addAnim("walk", "../example/assets/player_walk.png", {13, 16}, 5); interactables = tileMap.getTileCollection({"Mirror", "Bed", "Desk"}); } @@ -16,7 +16,7 @@ void Player::update(const double dt) { if (onGround) { - if (kn::input::isKeyPressed(kn::S_SPACE)) + if (kn::key::isPressed(kn::S_SPACE)) { velocity.y = -200; onGround = false; @@ -25,20 +25,20 @@ void Player::update(const double dt) else velocity.y += 980.0 * dt; - const auto dirVec = kn::input::getVector({kn::S_a}, {kn::S_d}); - if (dirVec.x != 0.0) + const int xDir = kn::key::isPressed(kn::S_d) - kn::key::isPressed(kn::S_a); + if (xDir != 0) { animController.setAnim("walk"); - if (dirVec.x > 0.0) + if (xDir > 0) facingRight = true; - else if (dirVec.x < 0.0) + else if (xDir < 0) facingRight = false; } else animController.setAnim("idle"); - velocity.x = dirVec.x * moveSpeed; + velocity.x = xDir * moveSpeed; rect.x += static_cast(velocity.x * dt); handleCollision(AxisX); diff --git a/include/AnimationController.hpp b/include/AnimationController.hpp index 953c208..aa20b3c 100644 --- a/include/AnimationController.hpp +++ b/include/AnimationController.hpp @@ -10,7 +10,7 @@ namespace kn { /** - * @brief Container for a texture pointer and rect + * @brief Container for a texture pointer and source rect */ struct Frame { @@ -18,6 +18,15 @@ struct Frame Rect rect; }; +/** + * @brief Container for a list of frames. + */ +struct Animation +{ + std::vector frames; + int fps; +}; + /** * @brief Handler for sprite sheet animations. */ @@ -25,25 +34,30 @@ class AnimationController final { public: /** - * @brief Construct a new Animation Controller object at a given frame rate. - * - * @param fps The frame rate of the animation. + * @brief Construct a new Animation Controller object. */ - explicit AnimationController(int fps = 24); + AnimationController() = default; ~AnimationController() = default; /** * @brief Set up the animation controller. * - * @param filePath The path to the sprite sheet image file. * @param name The name of the animation. - * @param frameWidth The width of each frame. - * @param frameHeight The height of each frame. + * @param filePath The path to the sprite sheet image file. + * @param frameSize The size of each frame in the sprite sheet. + * @param fps The frame rate of the animation. * * @return true if the setup was successful, false otherwise. */ - [[maybe_unused]] bool addAnim(const std::string& filePath, const std::string& name, - int frameWidth, int frameHeight); + [[maybe_unused]] bool addAnim(const std::string& name, const std::string& filePath, + const math::Vec2& frameSize, int fps); + + /** + * @brief Remove an animation from the controller. + * + * @param name The name of the animation to remove. + */ + void removeAnim(const std::string& name); /** * @brief Change the active animation. @@ -64,13 +78,26 @@ class AnimationController final [[nodiscard]] const Frame& nextFrame(double deltaTime); /** - * @brief Set the frame rate of the animation. + * @brief Set the playback speed of animations. * - * @param fps The frame rate of the animation. + * @param speed The speed to animate. + * YES, IT WORKS WITH NEGATIVE VALUES! + */ + void setPlaybackSpeed(double speed); + + /** + * @brief Check if the current animation playing is finished. * - * @note Will pause the animation if set to 0. + * @return true if the animation is finished, false otherwise. */ - void setFPS(int fps); + [[nodiscard]] bool isFinished(); + + /** + * @brief Get the current animation name. + * + * @return The name of the current animation. + */ + [[nodiscard]] const std::string& getCurrentAnim() const { return m_currAnim; } /** * @brief Pause the animation. @@ -85,14 +112,12 @@ class AnimationController final void resume(); private: - int m_fps; - int m_index = 0; + double m_playbackSpeed = 1.0; + double m_index = 0.0; + double m_prevIndex = 0.0; bool m_paused = false; std::string m_currAnim; - double m_frameTime_ms; - double m_timeLeftInFrame_ms = 0.0; - - std::unordered_map> m_animMap; + std::unordered_map m_animMap; }; } // namespace kn diff --git a/include/GameController.hpp b/include/GameController.hpp new file mode 100644 index 0000000..45f1799 --- /dev/null +++ b/include/GameController.hpp @@ -0,0 +1,60 @@ +#pragma once + +namespace kn +{ +namespace math +{ +class Vec2; +} // namespace math + +namespace controller +{ +/** + * @brief Get the direction vector from the controller's left joystick. + * + * @return A direction vector. If the controller is not connected, a zero vector is returned. + */ +[[nodiscard]] math::Vec2 getLeftJoystick(); + +/** + * @brief Get the direction vector from the controller's right joystick. + * + * @return A direction vector. If the controller is not connected, a zero vector is returned. + */ +[[nodiscard]] math::Vec2 getRightJoystick(); + +/** + * @brief Get how far the controller's left trigger is pressed. + * + * @return The value of the left trigger from the range 0.0 to 1.0. If the controller is not + * connected, 0.0 is returned. + */ +[[nodiscard]] double getLeftTrigger(); + +/** + * @brief Get how far the controller's right trigger is pressed. + * + * @return The value of the right trigger from the range 0.0 to 1.0. If the controller is not + * connected, 0.0 is returned. + */ +[[nodiscard]] double getRightTrigger(); + +/** +* @brief Change the dead zone for controller joystick input. +* +* @param deadZone The dead zone for the controller from the range 0.0 to 1.0. +* +* @note Negative values will be clamped to 0.0 and values greater than 1.0 will be clamped +to 1.0. +*/ +void setDeadZone(float deadZone); + +/** + * @brief Get the dead zone for controller joystick input. + * + * @return The dead zone for the controller from the range 0.0 to 1.0. + */ +[[nodiscard]] float getDeadZone(); + +} // namespace controller +} // namespace kn diff --git a/include/Input.hpp b/include/Input.hpp deleted file mode 100644 index c26e4b3..0000000 --- a/include/Input.hpp +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include - -#include "Constants.hpp" - -namespace kn -{ -namespace math -{ -class Vec2; -} // namespace math - -namespace input -{ -/** - * @brief Get the mouse position. - * - * @return The mouse position. - */ -math::Vec2 getMousePos(); - -/** - * @brief Check if the argument mouse button is pressed. - * - * @param button The mouse button to check. - * - * @return If the argument mouse button is pressed. - */ -bool isMouseButtonPressed(int button); - -/** - * @brief Check if the argument key is pressed. - * - * @param key The key to check. - * - * @return If the argument key is pressed. - */ -bool isKeyPressed(int key); - -/** - * @brief Get the vector of the keys pressed. - * - * @param left The keys to move left. - * @param right The keys to move right. - * @param up The keys to move up. - * @param down The keys to move down. - * - * @return The vector of the keys pressed. - */ -math::Vec2 getVector(const std::vector& left = {}, const std::vector& right = {}, - const std::vector& up = {}, const std::vector& down = {}); -} // namespace input -} // namespace kn diff --git a/include/Key.hpp b/include/Key.hpp new file mode 100644 index 0000000..919525e --- /dev/null +++ b/include/Key.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace kn +{ +namespace math +{ +class Vec2; +} // namespace math + +namespace key +{ +/** + * @brief Check if the argument key is pressed. + * + * @param key The key to check. + * + * @return If the argument key is pressed. + */ +bool isPressed(int key); +} // namespace key +} // namespace kn \ No newline at end of file diff --git a/include/KrakenEngine.hpp b/include/KrakenEngine.hpp index 0ebabe9..c52381f 100644 --- a/include/KrakenEngine.hpp +++ b/include/KrakenEngine.hpp @@ -6,10 +6,11 @@ #include "Constants.hpp" #include "Draw.hpp" #include "Font.hpp" -#include "Input.hpp" +#include "GameController.hpp" +#include "Key.hpp" #include "Math.hpp" +#include "Mouse.hpp" #include "Music.hpp" -#include "Overflow.hpp" #include "Rect.hpp" #include "Sound.hpp" #include "Texture.hpp" diff --git a/include/Mouse.hpp b/include/Mouse.hpp new file mode 100644 index 0000000..a6f1fac --- /dev/null +++ b/include/Mouse.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +namespace kn +{ +namespace math +{ +class Vec2; +} // namespace math + +namespace mouse +{ +/** + * @brief Get the mouse position. + * + * @return The mouse position as a vector. + */ +math::Vec2 getPos(); + +/** + * @brief Check if the argument mouse button is pressed. + * + * @param button The mouse button to check. + * + * @return If the argument mouse button is pressed. + */ +bool isPressed(uint32_t button); + +} // namespace mouse +} // namespace kn \ No newline at end of file diff --git a/include/Rect.hpp b/include/Rect.hpp index e8fd099..e24a613 100644 --- a/include/Rect.hpp +++ b/include/Rect.hpp @@ -18,13 +18,17 @@ struct Rect : SDL_FRect /** * @brief Construct a new rectangle. * - * @param x The x position. - * @param y The y position. + * @param x The x ordinate of its top left. + * @param y The y ordinate of its top left. * @param w The width. * @param h The height. */ - Rect(float x, float y, float w, float h); - Rect(int x, int y, int w, int h); + template + Rect(T x, T y, T w, T h) + : SDL_FRect{static_cast(x), static_cast(y), static_cast(w), + static_cast(h)} + { + } /** * @brief Get the position of the rectangle. diff --git a/include/Time.hpp b/include/Time.hpp index 2c3c9c5..a5eea90 100644 --- a/include/Time.hpp +++ b/include/Time.hpp @@ -16,9 +16,9 @@ class Clock final ~Clock() = default; /** - * @brief Get the delta time between frames. + * @brief Get the delta time between frames in milliseconds. * - * @param frameRate The frame rate to calculate the delta time. + * @param frameRate The frame rate to cap the program at. * * @return The delta time between frames. */ @@ -27,10 +27,9 @@ class Clock final private: double m_frameTime = 0.0; double m_targetFrameTime = 0.0; - double m_deltaTime = 0.0; double m_frequency = static_cast(SDL_GetPerformanceFrequency()); double m_now = static_cast(SDL_GetPerformanceCounter()); - double m_last = static_cast(SDL_GetPerformanceCounter()); + double m_last = m_now; }; /** diff --git a/include/Window.hpp b/include/Window.hpp index f86fc87..140659f 100644 --- a/include/Window.hpp +++ b/include/Window.hpp @@ -26,8 +26,23 @@ namespace window * @param resolution The size of the window. * @param title The title of the window. * @param scale The scale of the window. + * + * @return Whether the window was successfully initialized. + */ +[[maybe_unused]] bool init(const math::Vec2& resolution, const std::string& title = "Kraken Window", + int scale = 1); + +/** + * @brief Get whether the window is open. + * + * @return Whether the window is open. */ -void init(const math::Vec2& resolution, const std::string& title = "Kraken Window", int scale = 1); +[[nodiscard]] bool isOpen(); + +/** + * @brief Close the window. + */ +void close(); /** * @brief Clear the screen. @@ -63,35 +78,37 @@ void blit(const Texture& texture, const math::Vec2& position = {}); * * @return The window renderer. */ -SDL_Renderer* getRenderer(); +[[nodiscard]] SDL_Renderer* getRenderer(); /** - * @brief Get user events. + * @brief Populate the event object with the concurrent user events. + * + * @param event The event object to populate. * - * @return The user events. + * @return */ -const std::vector& getEvents(); +int pollEvent(Event& event); /** * @brief Get whether the window is fullscreen or not. * * @return Whether the window is fullscreen or not. */ -bool getFullscreen(); +[[nodiscard]] bool getFullscreen(); /** * @brief Get the scale of the window. * * @return The scale of the window. */ -int getScale(); +[[nodiscard]] int getScale(); /** * @brief Get the size of the screen. * * @return The size of the screen. */ -math::Vec2 getSize(); +[[nodiscard]] math::Vec2 getSize(); /** * @brief Set the title of the window. @@ -105,7 +122,7 @@ void setTitle(const std::string& newTitle); * * @return The title of the window. */ -std::string getTitle(); +[[nodiscard]] std::string getTitle(); /** * @brief Set whether the window is fullscreen or not. diff --git a/include/_globals.hpp b/include/_globals.hpp new file mode 100644 index 0000000..f8a7023 --- /dev/null +++ b/include/_globals.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "Math.hpp" +#include + +namespace kn +{ +inline SDL_GameController* _controller; +} // namespace kn diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3627f76 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +meson +ninja +pre-commit \ No newline at end of file diff --git a/src/animation_controller.cpp b/src/animation_controller.cpp index e97ae7c..e46ced6 100644 --- a/src/animation_controller.cpp +++ b/src/animation_controller.cpp @@ -1,33 +1,20 @@ #include "AnimationController.hpp" -#include "Constants.hpp" #include "ErrorLogger.hpp" #include "Math.hpp" -#include "Overflow.hpp" namespace kn { -AnimationController::AnimationController(const int fps) : m_fps(fps) +bool AnimationController::addAnim(const std::string& name, const std::string& filePath, + const math::Vec2& frameSize, const int fps) { - m_frameTime_ms = (fps == 0) ? 0.0 : MILLISECONDS_PER_SECOND / fps; -} - -bool AnimationController::addAnim(const std::string& filePath, const std::string& name, - const int frameWidth, const int frameHeight) -{ - std::shared_ptr texPtr(new Texture()); + const std::shared_ptr texPtr(new Texture()); if (!texPtr->loadFromFile(filePath)) return false; - if (m_animMap.find(name) != m_animMap.end()) // if animation name already exists - { - m_animMap.at(name).clear(); - m_timeLeftInFrame_ms = (m_fps == 0) ? 0.0 : m_frameTime_ms; - m_index = 0; - } - - m_animMap[name] = {}; const math::Vec2 size = texPtr->getSize(); + const int frameWidth = static_cast(frameSize.x); + const int frameHeight = static_cast(frameSize.y); if (static_cast(size.x) % frameWidth || static_cast(size.y) % frameHeight) { @@ -35,25 +22,34 @@ bool AnimationController::addAnim(const std::string& filePath, const std::string return false; } + if (m_animMap.find(name) != m_animMap.end()) + m_animMap.erase(name); + + Animation newAnim; + newAnim.fps = fps; for (int x = 0; x < size.x; x += frameWidth) for (int y = 0; y < size.y; y += frameHeight) { const Frame frame{texPtr, {x, y, frameWidth, frameHeight}}; - m_animMap.at(name).emplace_back(frame); + newAnim.frames.emplace_back(frame); } + m_animMap[name] = std::move(newAnim); m_currAnim = name; return true; } +void AnimationController::removeAnim(const std::string& name) +{ + if (m_animMap.find(name) != m_animMap.end()) + m_animMap.erase(name); +} + bool AnimationController::setAnim(const std::string& name) { if (m_animMap.find(name) == m_animMap.end()) - { - ERROR(name + " does not exist in animation controller.") return false; - } m_currAnim = name; return true; @@ -61,58 +57,41 @@ bool AnimationController::setAnim(const std::string& name) const Frame& AnimationController::nextFrame(const double deltaTime) { - if (m_paused) - return m_animMap.at(m_currAnim).at(m_index); + Animation& currAnim = m_animMap.at(m_currAnim); - if (!isProductValid(static_cast(MILLISECONDS_PER_SECOND), deltaTime)) - { - ERROR("Cannot multiply values"); - return m_animMap.at(m_currAnim).at(m_index); - } + if (m_paused) + return currAnim.frames.at(static_cast(m_index)); - if (const double msTakenLastFrame = MILLISECONDS_PER_SECOND * deltaTime; - m_timeLeftInFrame_ms < msTakenLastFrame) - { - const int numberOfFramesPassed = - static_cast((msTakenLastFrame - m_timeLeftInFrame_ms) / m_frameTime_ms) + 1; - m_timeLeftInFrame_ms = m_frameTime_ms; - m_index = - (m_index + numberOfFramesPassed) % static_cast(m_animMap.at(m_currAnim).size()); - } - else - { - if (!isSumValid(m_timeLeftInFrame_ms, -msTakenLastFrame)) - { - ERROR("Cannot subtract values"); - return m_animMap.at(m_currAnim).at(m_index); - } - m_timeLeftInFrame_ms -= msTakenLastFrame; - } + m_index += deltaTime * currAnim.fps * m_playbackSpeed; + m_index = fmod(m_index + static_cast(currAnim.frames.size()), currAnim.frames.size()); - return m_animMap.at(m_currAnim).at(m_index); + return currAnim.frames.at(static_cast(m_index)); } void AnimationController::pause() { m_paused = true; } void AnimationController::resume() { - if (m_fps > 0) + if (m_playbackSpeed > 0.0) m_paused = false; } -void AnimationController::setFPS(const int fps) +void AnimationController::setPlaybackSpeed(const double speed) { - m_fps = fps; + m_playbackSpeed = speed; + if (speed == 0) + pause(); +} - if (m_fps == 0) +bool AnimationController::isFinished() +{ + if (m_prevIndex > m_index) { - pause(); - m_frameTime_ms = 0.0; + m_prevIndex = m_index; + return true; } - else - m_frameTime_ms = MILLISECONDS_PER_SECOND / m_fps; - - m_timeLeftInFrame_ms = m_frameTime_ms; + m_prevIndex = m_index; + return false; } } // namespace kn diff --git a/src/game_controller.cpp b/src/game_controller.cpp new file mode 100644 index 0000000..1843e95 --- /dev/null +++ b/src/game_controller.cpp @@ -0,0 +1,71 @@ +#include + +#include "GameController.hpp" +#include "Math.hpp" +#include "_globals.hpp" + +namespace kn::controller +{ +static float _deadZone = 0.1f; + +math::Vec2 getLeftJoystick() +{ + if (!_controller) + { + WARN("Game controller is not connected") + return {}; + } + + math::Vec2 controllerDir = {SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_LEFTX), + SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_LEFTY)}; + controllerDir /= 32767.0; + if (controllerDir.getLength() > _deadZone) + return controllerDir; + + return {}; +} + +math::Vec2 getRightJoystick() +{ + if (!_controller) + { + WARN("Game controller is not connected") + return {}; + } + + math::Vec2 controllerDir = {SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_RIGHTX), + SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_RIGHTY)}; + controllerDir /= 32767.0; + if (controllerDir.getLength() > _deadZone) + return controllerDir; + + return {}; +} + +double getLeftTrigger() +{ + if (!_controller) + { + WARN("Game controller is not connected") + return 0.0; + } + + return SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32768.0; +} + +double getRightTrigger() +{ + if (!_controller) + { + WARN("Game controller is not connected") + return 0.0; + } + + return SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32768.0; +} + +void setControllerDeadZone(const float deadZone) { _deadZone = std::clamp(deadZone, 0.0f, 1.0f); } + +float getControllerDeadZone() { return _deadZone; } + +} // namespace kn::controller diff --git a/src/input.cpp b/src/input.cpp deleted file mode 100644 index 800657f..0000000 --- a/src/input.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include - -#include "Constants.hpp" -#include "Input.hpp" -#include "Math.hpp" -#include "Window.hpp" - -namespace kn::input -{ -math::Vec2 getMousePos() -{ - int x, y; - SDL_GetMouseState(&x, &y); - const int scale = window::getScale(); - const math::Vec2 pos{x / scale, y / scale}; - return pos + camera; -} - -bool isMouseButtonPressed(const int button) { return SDL_GetMouseState(nullptr, nullptr) == button; } - -bool isKeyPressed(const int key) { return SDL_GetKeyboardState(nullptr)[key]; } - -math::Vec2 getVector(const std::vector& left, const std::vector& right, - const std::vector& up, const std::vector& down) -{ - math::Vec2 vector; - - if (std::any_of(up.begin(), up.end(), [&](auto scancode) { return isKeyPressed(scancode); })) - vector.y -= 1; - if (std::any_of(left.begin(), left.end(), [&](auto scancode) { return isKeyPressed(scancode); })) - vector.x -= 1; - if (std::any_of(down.begin(), down.end(), [&](auto scancode) { return isKeyPressed(scancode); })) - vector.y += 1; - if (std::any_of(right.begin(), right.end(), [&](auto scancode) { return isKeyPressed(scancode); })) - vector.x += 1; - - if (vector.getLength() > 0) - vector.normalize(); - - return vector; -} -} // namespace kn::input diff --git a/src/key.cpp b/src/key.cpp new file mode 100644 index 0000000..3055717 --- /dev/null +++ b/src/key.cpp @@ -0,0 +1,9 @@ +#include "Key.hpp" +#include + +namespace kn::key +{ + +bool isPressed(const int key) { return SDL_GetKeyboardState(nullptr)[key]; } + +} // namespace kn::key \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 224cacc..3d99cb6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,8 +2,10 @@ sources = [ 'src/animation_controller.cpp', 'src/draw.cpp', 'src/font.cpp', - 'src/input.cpp', + 'src/game_controller.cpp', + 'src/key.cpp', 'src/math.cpp', + 'src/mouse.cpp', 'src/music.cpp', 'src/overflow.cpp', 'src/rect.cpp', diff --git a/src/mouse.cpp b/src/mouse.cpp new file mode 100644 index 0000000..e5dff1d --- /dev/null +++ b/src/mouse.cpp @@ -0,0 +1,16 @@ +#include "Mouse.hpp" +#include "Math.hpp" +#include "Window.hpp" + +namespace kn::mouse +{ +math::Vec2 getPos() +{ + int x, y; + SDL_GetMouseState(&x, &y); + const math::Vec2 pos{x, y}; + return pos / window::getScale() + camera; +} + +bool isPressed(const uint32_t button) { return SDL_GetMouseState(nullptr, nullptr) == button; } +} // namespace kn::mouse diff --git a/src/rect.cpp b/src/rect.cpp index 833a78f..b598449 100644 --- a/src/rect.cpp +++ b/src/rect.cpp @@ -7,14 +7,6 @@ math::Vec2 Rect::getPos() const { return {x, y}; } math::Vec2 Rect::getSize() const { return {w, h}; } -Rect::Rect(const float x, const float y, const float w, const float h) : SDL_FRect{x, y, w, h} {} - -Rect::Rect(const int x, const int y, const int w, const int h) - : SDL_FRect{static_cast(x), static_cast(y), static_cast(w), - static_cast(h)} -{ -} - void Rect::setSize(const math::Vec2& size) { w = static_cast(size.x); diff --git a/src/tile_map.cpp b/src/tile_map.cpp index 2df68fd..2419b90 100644 --- a/src/tile_map.cpp +++ b/src/tile_map.cpp @@ -180,7 +180,6 @@ Rect TileMap::getFittedRect(const SDL_Surface* surface, const Rect& srcRect, const int bytesPerPixel = surface->format->BytesPerPixel; for (int y = rectInt.y; y < rectInt.y + rectInt.h; y++) - { for (int x = rectInt.x; x < rectInt.x + rectInt.w; x++) { const Uint8* pixel = pixels + y * pitch + x * bytesPerPixel; @@ -192,7 +191,6 @@ Rect TileMap::getFittedRect(const SDL_Surface* surface, const Rect& srcRect, left = std::min(left, x - rectInt.x); right = std::max(right, x - rectInt.x); } - } return {static_cast(position.x + left), static_cast(position.y + top), right - left + 1, bottom - top + 1}; diff --git a/src/time.cpp b/src/time.cpp index a08ee40..0422e5e 100644 --- a/src/time.cpp +++ b/src/time.cpp @@ -3,30 +3,26 @@ namespace kn::time { + double Clock::tick(int frameRate) { if (frameRate < 1) frameRate = 1; m_targetFrameTime = 1000.0 / frameRate; - m_frameTime = - (static_cast(SDL_GetPerformanceCounter()) / m_frequency - m_last / m_frequency) * - 1000.0; + m_now = static_cast(SDL_GetPerformanceCounter()); + m_frameTime = (m_now - m_last) / m_frequency * 1000.0; + if (m_frameTime < m_targetFrameTime) SDL_Delay(static_cast(m_targetFrameTime - m_frameTime)); m_now = static_cast(SDL_GetPerformanceCounter()); - m_deltaTime = m_now / m_frequency - m_last / m_frequency; + m_frameTime = (m_now - m_last) / m_frequency * 1000.0; m_last = m_now; - return std::min(m_deltaTime, 0.033); // Limit delta time at 33ms or 30fps + return m_frameTime; } -double getTicks() -{ - const auto now = static_cast(SDL_GetPerformanceCounter()); - const auto frequency = static_cast(SDL_GetPerformanceFrequency()); - return now / frequency; -} +double getTicks() { return SDL_GetTicks() / 1000.0; } } // namespace kn::time \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index 0c7415a..2a480bd 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -5,84 +5,136 @@ #include "ErrorLogger.hpp" #include "Music.hpp" #include "Window.hpp" +#include "_globals.hpp" namespace kn::window { static SDL_Renderer* _renderer; static SDL_Window* _window; -static Event _event; -static std::vector _events; +static bool _isOpen; -void init(const math::Vec2& resolution, const std::string& title, const int scale) +bool init(const math::Vec2& resolution, const std::string& title, const int scale) { if (_renderer) + { WARN("Cannot initialize renderer more than once") + return false; + } - if (SDL_Init(SDL_INIT_VIDEO)) + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) + { FATAL("SDL_Init Error: " + std::string(SDL_GetError())) + return false; + } + if (!IMG_Init(IMG_INIT_PNG)) { FATAL("IMG_Init Error: " + std::string(IMG_GetError())) SDL_Quit(); + return false; } + if (TTF_Init()) { FATAL("TTF_Init Error: " + std::string(TTF_GetError())) IMG_Quit(); SDL_Quit(); + return false; } + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048)) { FATAL("Mix_OpenAudio Error: " + std::string(Mix_GetError())) TTF_Quit(); IMG_Quit(); SDL_Quit(); + return false; } + const int resolutionWidth = static_cast(resolution.x); + const int resolutionHeight = static_cast(resolution.y); _window = SDL_CreateWindow("Kraken Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - static_cast(resolution.x) * scale, - static_cast(resolution.y) * scale, SDL_WINDOW_SHOWN); + resolutionWidth * scale, resolutionHeight * scale, SDL_WINDOW_SHOWN); if (!_window) + { FATAL("SDL_CreateWindow Error: " + std::string(SDL_GetError())) + return false; + } _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); if (!_renderer) - FATAL("SDL_CreateRenderer Error: " + std::string(SDL_GetError())); - - if (scale > 1) { - SDL_RenderSetLogicalSize(_renderer, static_cast(resolution.x), - static_cast(resolution.y)); + FATAL("SDL_CreateRenderer Error: " + std::string(SDL_GetError())) + return false; } + if (scale > 1) + SDL_RenderSetLogicalSize(_renderer, resolutionWidth, resolutionHeight); + setTitle(title); setIcon("../example/assets/kraken_engine_window_icon.png"); + + _isOpen = true; + + return true; } +bool isOpen() { return _isOpen; } + +void close() { _isOpen = false; } + void quit() { + music::unload(); + if (_renderer) SDL_DestroyRenderer(_renderer); if (_window) SDL_DestroyWindow(_window); + if (_controller) + SDL_GameControllerClose(_controller); - music::unload(); Mix_CloseAudio(); IMG_Quit(); TTF_Quit(); SDL_Quit(); } -const std::vector& getEvents() +int pollEvent(Event& event) { if (!_window) WARN("Cannot get events before creating the window") - _events.clear(); - while (SDL_PollEvent(&_event)) - _events.push_back(_event); + const int pending = SDL_PollEvent(&event); + + if (pending) + { + switch (event.type) + { + case QUIT: + close(); + break; + case CONTROLLERDEVICEADDED: + { + if (!_controller) + _controller = SDL_GameControllerOpen(event.cdevice.which); + break; + } + case CONTROLLERDEVICEREMOVED: + if (_controller && + event.cdevice.which == + SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(_controller))) + { + SDL_GameControllerClose(_controller); + _controller = nullptr; + } + break; + default: + break; + } + } - return _events; + return pending; } void clear(const Color color)