diff --git a/include/vkcv/Window.hpp b/include/vkcv/Window.hpp
index f71671c935a0a5e17bb517c726d75ffff2973532..59a8826e62cf77b661e1ba7e1a84ee367c3fb48a 100644
--- a/include/vkcv/Window.hpp
+++ b/include/vkcv/Window.hpp
@@ -39,6 +39,12 @@ namespace vkcv {
          */
         static void onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods);
 
+        /**
+         * @brief A callback function for handling mouse scrolling events.
+         * @param[in] callbackWindow The window that received the event.
+         * @param[in] xoffset The extent of horizontal scrolling.
+         * @param[in] yoffset The extent of vertical scrolling.
+         */
         static void onMouseScrollEvent(GLFWwindow *callbackWindow, double xoffset, double yoffset);
 
         /**
@@ -59,6 +65,11 @@ namespace vkcv {
          */
         static void onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods);
 
+        /**
+         * @brief TODO
+         */
+        static void onGamepadEvent(int gamepadIndex, Window *window);
+
     public:
         /**
          * creates a GLFWwindow with the parameters in the function
@@ -85,7 +96,7 @@ namespace vkcv {
         /**
          * polls all events on the GLFWwindow
          */
-        static void pollEvents();
+        void pollEvents();
 
         /**
          * basic events of the window
@@ -95,6 +106,7 @@ namespace vkcv {
         event< double, double > e_mouseScroll;
         event< int, int > e_resize;
         event< int, int, int, int > e_key;
+        event< int > e_gamepad;
 
         /**
          * returns the current window
diff --git a/modules/camera/include/vkcv/camera/CameraController.hpp b/modules/camera/include/vkcv/camera/CameraController.hpp
index 5fe7aba586068beff15525617d8e4817662746b7..5a534f9167d170795913c6710182a03373716e64 100644
--- a/modules/camera/include/vkcv/camera/CameraController.hpp
+++ b/modules/camera/include/vkcv/camera/CameraController.hpp
@@ -59,6 +59,12 @@ namespace vkcv::camera {
          * @param[in] camera The camera object.
          */
         virtual void mouseButtonCallback(int button, int action, int mods, Camera &camera) = 0;
+
+        /**
+         * @brief todo
+         * @param gamepadIndex
+         */
+        virtual void gamepadCallback(int gamepadIndex, Camera &camera) = 0;
     };
 
 }
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
index 69c4b311a561b9f3ce27e3d9906d68f21e2334ac..bb7e3df2eba47e5dad8e9b548e3c7ba2dad7883a 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -30,6 +30,7 @@ namespace vkcv::camera {
         std::function<void(double, double)> m_mouseScrollHandle;
         std::function<void(int, int, int)> m_mouseButtonHandle;
         std::function<void(int, int)> m_resizeHandle;
+        std::function<void(int)> m_gamepadHandle;
 
         Window& m_window;
         std::vector<Camera> m_cameras;
@@ -42,6 +43,8 @@ namespace vkcv::camera {
         double m_lastX;
         double m_lastY;
 
+        double m_inputDelayTimer;
+
         /**
          * @brief Binds the camera object to the window event handles.
          */
@@ -86,6 +89,11 @@ namespace vkcv::camera {
          * @param[in] height The new height of the window.
          */
         void resizeCallback(int width, int height);
+
+        /**
+         * @brief TODO
+         */
+        void gamepadCallback(int gamepadIndex);
 	
 		/**
 		 * @brief Gets a camera controller object of specified @p controllerType.
diff --git a/modules/camera/include/vkcv/camera/PilotCameraController.hpp b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
index c6a9f7c7ffa9a3be77f12c29e456291fb8f6b845..349544d28b825289d80a71d677e84a0ed205ed99 100644
--- a/modules/camera/include/vkcv/camera/PilotCameraController.hpp
+++ b/modules/camera/include/vkcv/camera/PilotCameraController.hpp
@@ -17,6 +17,10 @@ namespace vkcv::camera {
         bool m_left;
         bool m_right;
 
+        float m_gamepadX;
+        float m_gamepadY;
+        float m_gamepadZ;
+
         bool m_rotationActive;
 
         float m_cameraSpeed;
@@ -133,6 +137,12 @@ namespace vkcv::camera {
          * @param[in] camera The camera object.
          */
         void mouseButtonCallback(int button, int action, int mods, Camera &camera);
+
+        /**
+         * @brief todo
+         * @param gamepadIndex
+         */
+        void gamepadCallback(int gamepadIndex, Camera &camera);
     };
 
 }
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
index 0211043a9c6b862df8e500af190ad1f75a3c78aa..58b78dca5cd35f85149f0e571cd74d0a49851003 100644
--- a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
+++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp
@@ -95,6 +95,11 @@ namespace vkcv::camera {
          */
         void mouseButtonCallback(int button, int action, int mods, Camera &camera);
 
+        /**
+         * @brief todo
+         * @param gamepadIndex
+         */
+        void gamepadCallback(int gamepadIndex, Camera &camera);
     };
 
 }
\ No newline at end of file
diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp
index 977c61d03dac51598281262acf9609540063a9e4..567abf5500ff4dcd5abaec7afd1f6d6f5ab8cebb 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -1,6 +1,5 @@
 
 #include "vkcv/camera/CameraManager.hpp"
-
 #include <vkcv/Logger.hpp>
 
 namespace vkcv::camera {
@@ -12,6 +11,7 @@ namespace vkcv::camera {
         m_activeCameraIndex = 0;
         m_lastX = static_cast<float>(window.getWidth()) / 2.0f;
         m_lastY = static_cast<float>(window.getHeight()) / 2.0f;
+        m_inputDelayTimer = glfwGetTime() + 0.2;
     }
 
     CameraManager::~CameraManager() {}
@@ -22,6 +22,7 @@ namespace vkcv::camera {
         m_mouseScrollHandle =  m_window.e_mouseScroll.add([&](double offsetX, double offsetY) {this->scrollCallback( offsetX, offsetY);} );
         m_mouseButtonHandle = m_window.e_mouseButton.add([&] (int button, int action, int mods) {this->mouseButtonCallback( button,  action,  mods);});
         m_resizeHandle = m_window.e_resize.add([&](int width, int height) {this->resizeCallback(width, height);});
+        m_gamepadHandle = m_window.e_gamepad.add([&](int gamepadIndex) {this->gamepadCallback(gamepadIndex);});
     }
 
     void CameraManager::resizeCallback(int width, int height) {
@@ -75,7 +76,29 @@ namespace vkcv::camera {
                 break;
         }
     }
-    
+
+    // todo: fix event catch speed
+    void CameraManager::gamepadCallback(int gamepadIndex) {
+        // handle camera switching
+        GLFWgamepadstate gamepadState;
+        glfwGetGamepadState(gamepadIndex, &gamepadState);
+
+        double time = glfwGetTime();
+        if (time - m_inputDelayTimer > 0.2) {
+            int switchDirection = gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] - gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT];
+            m_activeCameraIndex += switchDirection;
+            if (std::greater<int>{}(m_activeCameraIndex, m_cameras.size() - 1)) {
+                m_activeCameraIndex = 0;
+            }
+            else if (std::less<int>{}(m_activeCameraIndex, 0)) {
+                m_activeCameraIndex = m_cameras.size() - 1;
+            }
+            m_inputDelayTimer = time;
+        }
+
+        getActiveController().gamepadCallback(gamepadIndex, getActiveCamera());     // handle camera rotation, translation
+    }
+
     CameraController& CameraManager::getActiveController() {
     	const ControllerType type = getControllerType(getActiveCameraIndex());
     	return getControllerByType(type);
diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
index 1a50a0efa4b4e75adb81ce869d6b927bd0046758..13ae0b2bbba270e1ba46ba5f788d9cd7ee80922b 100644
--- a/modules/camera/src/vkcv/camera/PilotCameraController.cpp
+++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp
@@ -1,5 +1,4 @@
 #include "vkcv/camera/PilotCameraController.hpp"
-
 #include <GLFW/glfw3.h>
 
 namespace vkcv::camera {
@@ -12,9 +11,13 @@ namespace vkcv::camera {
         m_left = false;
         m_right = false;
 
+        m_gamepadX = 0.0f;
+        m_gamepadY = 0.0f;
+        m_gamepadZ = 0.0f;
+
         m_rotationActive = false;
 
-        m_cameraSpeed = 2.0f;
+        m_cameraSpeed = 2.5f;
 
         m_fov_nsteps = 100;
         m_fov_min = 10;
@@ -70,9 +73,9 @@ namespace vkcv::camera {
 	
 		const float distance = m_cameraSpeed * static_cast<float>(deltaTime);
 	
-		position += distance * getDirectionFactor(m_forward, m_backward) * front;
-		position += distance * getDirectionFactor(m_left, m_right) * left;
-		position += distance * getDirectionFactor(m_upward, m_downward) * up;
+		position += distance * (getDirectionFactor(m_forward, m_backward) + m_gamepadZ) * front;
+		position += distance * (getDirectionFactor(m_left, m_right) + m_gamepadX) * left;
+		position += distance * (getDirectionFactor(m_upward, m_downward) + m_gamepadY) * up;
 	
 		camera.lookAt(position, position + front, up);
     }
@@ -127,6 +130,45 @@ namespace vkcv::camera {
         }
     }
 
+    void PilotCameraController::gamepadCallback(int gamepadIndex, Camera &camera) {
+        GLFWgamepadstate gamepadState;
+        glfwGetGamepadState(gamepadIndex, &gamepadState);
+
+        float sensitivity = 0.05f;
+        double threshold = 0.03;    // todo: needs further investigation!
+
+        // handle rotations
+        double stickRightX = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
+        double stickRightY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+        if ((std::less<double>{}(stickRightX, -threshold) || std::greater<double>{}(stickRightX, threshold))
+            && (std::less<double>{}(stickRightY, -threshold) || std::greater<double>{}(stickRightY, threshold))) {
+            panView(stickRightX * sensitivity, stickRightY * sensitivity, camera);
+        }
+
+        // handle zooming
+        double zoom = static_cast<double>((gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]
+                - gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER])
+                * sensitivity * 0.5);
+        changeFov(zoom, camera);
+
+        // handle translation
+        m_gamepadY = gamepadState.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] - gamepadState.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER];
+        float stickLeftX = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_X];
+        float stickLeftY = gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y];
+        if (std::less<float>{}(stickLeftY, -threshold) || std::greater<float>{}(stickLeftY, threshold)) {
+            m_gamepadZ = -stickLeftY;
+        }
+        else {
+            m_gamepadZ = 0.0f;
+        }
+        if (std::less<float>{}(stickLeftX, -threshold) || std::greater<float>{}(stickLeftX, threshold)) {
+            m_gamepadX = -stickLeftX;
+        }
+        else {
+            m_gamepadX = 0.0f;
+        }
+    }
+
 
     void PilotCameraController::moveForward(int action){
         m_forward = static_cast<bool>(action);
diff --git a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
index 201c6ecdc1c703dbcd53b7dc4b179c86576f2312..71ab484d6ac93719cd6436644239d42bd0fdd1d7 100644
--- a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
+++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp
@@ -1,5 +1,4 @@
 #include "vkcv/camera/TrackballCameraController.hpp"
-
 #include <GLFW/glfw3.h>
 
 namespace vkcv::camera {
@@ -82,7 +81,7 @@ namespace vkcv::camera {
             return;
         }
 
-        float sensitivity = 0.05f;
+        float sensitivity = 0.025f;
         xoffset *= sensitivity;
         yoffset *= sensitivity;
 
@@ -97,4 +96,26 @@ namespace vkcv::camera {
             m_rotationActive = false;
         }
     }
+
+    void TrackballCameraController::gamepadCallback(int gamepadIndex, Camera &camera) {
+        GLFWgamepadstate gamepadState;
+        glfwGetGamepadState(gamepadIndex, &gamepadState);
+
+        float sensitivity = 0.025f;
+        double threshold = 0.03;    // todo: needs further investigation!
+
+        // handle rotations
+        double stickRightX = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
+        double stickRightY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+        if ((std::less<double>{}(stickRightX, -threshold) || std::greater<double>{}(stickRightX, threshold))
+            && (std::less<double>{}(stickRightY, -threshold) || std::greater<double>{}(stickRightY, threshold))) {
+            panView(stickRightX * sensitivity, stickRightY * sensitivity, camera);
+        }
+
+        // handle translation
+        double stickLeftY = static_cast<double>(gamepadState.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
+        if (std::less<double>{}(stickLeftY, -threshold) || std::greater<double>{}(stickLeftY, threshold)) {
+            updateRadius(-stickLeftY * sensitivity, camera);
+        }
+    }
 }
\ No newline at end of file
diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp
index a02c4542c7608e520e5ed33e746b5a9c836fc09f..ce2e0fbfaf06469030a99a7b248cb596c1c56fbf 100644
--- a/projects/cmd_sync_test/src/main.cpp
+++ b/projects/cmd_sync_test/src/main.cpp
@@ -223,7 +223,7 @@ int main(int argc, const char** argv) {
 	auto start = std::chrono::system_clock::now();
 	const auto appStartTime = start;
 	while (window.isWindowOpen()) {
-		vkcv::Window::pollEvents();
+		window.pollEvents();
 		
 		uint32_t swapchainWidth, swapchainHeight;
 		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 42b1f595dc9f36f8cfb9816a1508980aefb52a83..b33e514a3503f7ba748bea23843e29e3887e32d7 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -155,7 +155,7 @@ int main(int argc, const char** argv) {
     auto start = std::chrono::system_clock::now();
     
 	while (window.isWindowOpen()) {
-        vkcv::Window::pollEvents();
+        window.pollEvents();
 		
 		if(window.getHeight() == 0 || window.getWidth() == 0)
 			continue;
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 8ecea2b69906b017b321e3ca6002761fb8ffdd7d..4775afdcf89778d363e298996fa1ae1e92e944fa 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -160,15 +160,17 @@ int main(int argc, const char** argv) {
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 	
     vkcv::camera::CameraManager cameraManager(window, windowWidth, windowHeight);
-    uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
-    uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+    uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+    uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
 	
-	cameraManager.getCamera(camIndex).setPosition(glm::vec3(0, 0, -2));
+	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2));
+    cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f));
+    cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
 
 	while (window.isWindowOpen())
 	{
         window.pollEvents();
-		
+
 		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
 		if (!core.beginFrame(swapchainWidth, swapchainHeight)) {
 			continue;
diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp
index c21271b78f7501721d5c0496d0344dd68e2e7e52..70cfb153833c528be39c5d2c292e0b5da0d04083 100644
--- a/src/vkcv/Window.cpp
+++ b/src/vkcv/Window.cpp
@@ -5,7 +5,6 @@
  */
 
 #include <GLFW/glfw3.h>
-
 #include "vkcv/Window.hpp"
 
 namespace vkcv {
@@ -55,14 +54,16 @@ namespace vkcv {
         glfwSetKeyCallback(m_window, Window::onKeyEvent);
 
         glfwSetScrollCallback(m_window, Window::onMouseScrollEvent);
+
+        glfwSetJoystickUserPointer(GLFW_JOYSTICK_1, this);
     }
 
     void Window::pollEvents() {
+        onGamepadEvent(GLFW_JOYSTICK_1, this);
         glfwPollEvents();
     }
 
     void Window::onMouseButtonEvent(GLFWwindow *callbackWindow, int button, int action, int mods) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -71,7 +72,6 @@ namespace vkcv {
     }
 
     void Window::onMouseMoveEvent(GLFWwindow *callbackWindow, double x, double y) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -88,7 +88,6 @@ namespace vkcv {
     }
 
     void Window::onResize(GLFWwindow *callbackWindow, int width, int height) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -97,7 +96,6 @@ namespace vkcv {
     }
 
     void Window::onKeyEvent(GLFWwindow *callbackWindow, int key, int scancode, int action, int mods) {
-
         auto window = static_cast<Window *>(glfwGetWindowUserPointer(callbackWindow));
 
         if (window != nullptr) {
@@ -105,6 +103,12 @@ namespace vkcv {
         }
     }
 
+    void Window::onGamepadEvent(int gamepadIndex, Window *window) {
+        if (glfwJoystickPresent(gamepadIndex)) {
+            window->e_gamepad(gamepadIndex);
+        }
+    }
+
     bool Window::isWindowOpen() const {
         return !glfwWindowShouldClose(m_window);
     }