diff --git a/.gitignore b/.gitignore
index cd44b8d8673cd84ae3bb4568083ebf783e8dc3db..a326e396ed35e9edee85b322f8fb4b9ea8fda3d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ build/
 cmake-build-debug/
 cmake-build-debug-coverage/
 .idea/
+imgui.ini
diff --git a/demo/imgui.ini b/demo/imgui.ini
deleted file mode 100644
index 4a5c20148e89683813a21aaba10843ac98bbf579..0000000000000000000000000000000000000000
--- a/demo/imgui.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[Window][Debug##Default]
-Pos=60,60
-Size=400,400
-Collapsed=0
-
diff --git a/demo/main.cpp b/demo/main.cpp
index 2c168f7a0430c7b714bc3a67285958911fb4bdd9..932f0bfd314abf1bbe0e1f5bb7df7bcfd1f83c62 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -13,6 +13,8 @@
 #include <fggl/debug/debug.h>
 #include <fggl/data/storage.hpp>
 
+#include <imgui.h>
+
 template <typename T> int sgn(T val) {
     return (T(0) < val) - (val < T(0));
 }
@@ -109,6 +111,7 @@ int main(int argc, char* argv[]) {
 
 	// build our main window
 	fggl::gfx::Window win( fggl::gfx::Input::instance() );
+	win.title("FGGL Demo");
 	win.fullscreen( true );
 
 	// opengl time
@@ -117,7 +120,7 @@ int main(int argc, char* argv[]) {
 
 	// debug layer
 	fggl::debug::DebugUI debug(win);
-	debug.visible(false);
+	debug.visible(true);
 
 	// storage API
 	fggl::data::Storage storage;
@@ -194,6 +197,8 @@ int main(int argc, char* argv[]) {
 
 	fggl::gfx::Input& input = fggl::gfx::Input::instance();
 
+	bool joystickWindow = true;
+
 	float time = 0.0f;
 	float dt = 16.0f;
 	while( !win.closeRequested() ) {
@@ -210,6 +215,97 @@ int main(int argc, char* argv[]) {
 			process_arcball(win, ecs, input, camEnt);
 		}
 
+		// imgui joystick debug
+		ImGui::Begin("Joysticks", &joystickWindow);
+		for ( int i=0; i<16; i++ ) {
+			bool present = input.hasJoystick(i);
+			std::string title = "Joystick " + std::to_string(i);
+
+			if ( ImGui::TreeNode(title.c_str()) ) {
+				ImGui::Text("present: %s", present ? "yes" : "no" );
+
+				if ( present ) {
+					const fggl::gfx::Joystick& joyData = input.joystick(i);
+					ImGui::Text( "%s", joyData.name );
+					ImGui::Text( "gamepad: %s", joyData.gamepad ? "yes" : "no" );
+					ImGui::Text( "axes: %d, buttons: %d, hats: %d", joyData.axisCount, joyData.buttonCount, joyData.hatCount);
+
+					if (ImGui::TreeNode("axis##2")) {
+						// dump data
+						for ( int axid = 0; axid < joyData.axisCount; axid++ ) {
+							ImGui::Text("axis %d, value: %f", axid, joyData.axes[axid] );
+						}
+						ImGui::TreePop();
+					}
+
+					if (ImGui::TreeNode("buttons##2")) {
+						// dump data
+						for ( int btnid = 0; btnid < joyData.buttonCount; btnid++ ) {
+							ImGui::Text("button %d, value: %s", btnid, joyData.buttons[btnid] == GLFW_PRESS ? "down" : "up" );
+						}
+						ImGui::TreePop();
+					}
+
+					if (ImGui::TreeNode("hats##2")) {
+						// dump data
+						for ( int btnid = 0; btnid < joyData.hatCount; btnid++ ) {
+							ImGui::Text("button %d, value: %d", btnid, joyData.hats[btnid] );
+						}
+						ImGui::TreePop();
+					}
+				}
+
+				ImGui::TreePop();
+				ImGui::Separator();
+			}
+
+		}
+		ImGui::End();
+
+		// imgui gamepad debug
+		ImGui::Begin("GamePad", &joystickWindow);
+		for ( int i=0; i<16; i++ ) {
+			std::string title = "GamePad " + std::to_string(i);
+
+			bool present = input.hasJoystick(i);
+			if ( ImGui::TreeNode(title.c_str()) ) {
+				ImGui::Text("present: %s", present ? "yes" : "no" );
+
+				if ( present ) {
+
+					if ( ImGui::TreeNode("buttons##2") ) {
+						for ( auto& btn : fggl::gfx::PadButtons ) {
+							auto label = fggl::gfx::PadButtonLabels[ (int) btn ];
+							ImGui::Text( "%s: %i %i %i", label.c_str(), 
+									input.padDown(i, btn),
+									input.padPressed(i, btn),
+									input.padReleased(i, btn)
+									);
+						}
+						ImGui::TreePop();
+					}
+
+					if ( ImGui::TreeNode("axes##2") ) {
+						for ( auto& axis : fggl::gfx::PadAxes ) {
+							auto label = fggl::gfx::PadAxisLabels[ (int) axis ];
+							ImGui::Text("%s: %f %f", label.c_str(),
+									input.padAxis(i, axis),
+									input.padAxisDelta(i, axis)
+								   );
+
+						}
+						ImGui::TreePop();
+					}
+
+				}
+
+				ImGui::TreePop();
+				ImGui::Separator();
+			}
+
+		}
+		ImGui::End();
+
 /*		float amount = glm::radians( time / 2048.0f * 360.0f );
 		auto spinners = ecs.getEntityWith<fggl::math::Transform>();
 		for ( auto entity : spinners ) {
diff --git a/fggl/gfx/input.cpp b/fggl/gfx/input.cpp
index 23dc9b997024e160c872a1c220dd56d68fbb986a..09d8df8ae9fff0862cea3c9d6262c7f982d5aefd 100644
--- a/fggl/gfx/input.cpp
+++ b/fggl/gfx/input.cpp
@@ -6,23 +6,36 @@
 
 using fggl::gfx::Input;
 
-Input::Input() : m_mouse_curr(), m_mouse_last(), m_joydata(), m_joysticks() {
+Input::Input() : m_mouse_curr(), m_mouse_last(), m_joydata(), m_joysticks(), m_pad_last(), m_pad_curr() {
 	clear();
 }
 
 void Input::clear() {
+	// reset mouse data
 	m_mouse_curr.cursor[0] = 0.0;
 	m_mouse_curr.cursor[1] = 0.0;
 	m_mouse_curr.scroll[0] = 0.0;
 	m_mouse_curr.scroll[1] = 0.0;
 	m_mouse_curr.buttons = 0x00;
 	m_mouse_last = m_mouse_curr;
+
+	// reset pad data
+	for ( auto& pad : m_pad_curr ) {
+		pad.present = false;
+		pad.buttons.reset();
+		for (int i=0; i<6; i++){
+			pad.axes[i] = 0.0f;
+		}
+	}
+	m_pad_last = m_pad_curr;
 }
 
 void Input::frame() {
 	m_mouse_last = m_mouse_curr;
 	m_mouse_curr.scroll[0] = 0.0;
 	m_mouse_curr.scroll[1] = 0.0;
+
+	m_pad_last = m_pad_curr;
 }
 
 void Input::mousePos(double x, double y) {
@@ -81,7 +94,7 @@ bool Input::mouseReleased(const MouseButton btn) const {
 }
 
 void Input::joystickConnect(int id, Joystick &data){
-	std::cout << "JOYSTICK TIME: " << data.name << std::endl;
+	// joystick data is polled, so we need to call this every frame >.<
 	m_joysticks[id] = true;
 	m_joydata[id] = data;
 }
@@ -92,6 +105,34 @@ void Input::joystickDisconnect( int id ){
 	m_joydata[id] = Joystick();
 }
 
-bool Input::joystick(int id) const {
+bool Input::hasJoystick(int id) const {
 	return m_joysticks[id];
 }
+
+const fggl::gfx::Joystick& Input::joystick(int id) const {
+	return m_joydata[id];
+}
+
+void Input::padState(int id, const PadState& state) {
+	m_pad_curr[id] = state;
+}
+
+bool Input::padDown(int id, PadButton btn) {
+	return m_pad_curr[id].buttons[(int)btn];
+}
+
+bool Input::padPressed(int id, PadButton btn) {
+	return m_pad_curr[id].buttons[(int)btn] && !m_pad_last[id].buttons[(int)btn];
+}
+
+bool Input::padReleased(int id, PadButton btn) {
+	return !m_pad_curr[id].buttons[(int)btn] && m_pad_last[id].buttons[(int)btn];
+}
+
+float Input::padAxis(int id, PadAxis axis) {
+	return m_pad_curr[id].axes[(int)axis];
+}
+
+float Input::padAxisDelta(int id, PadAxis axis) {
+	return m_pad_last[id].axes[(int)axis] - m_pad_curr[id].axes[(int)axis];
+}
diff --git a/fggl/gfx/input.hpp b/fggl/gfx/input.hpp
index 492284848eabce98e016648a6bab6e46fd2481fc..5e29c8b7bd61fda4fc182526a02383930663c710 100644
--- a/fggl/gfx/input.hpp
+++ b/fggl/gfx/input.hpp
@@ -23,6 +23,89 @@ namespace fggl::gfx {
 		char buttons;
 	};
 
+	enum class PadButton {
+		A,
+		B,
+		X,
+		Y,
+		BUMPER_LEFT,
+		BUMPER_RIGHT,
+		BACK,
+		START,
+		GUIDE,
+		THUMB_LEFT,
+		THUMB_RIGHT,
+		DPAD_UP,
+		DPAD_RIGHT,
+		DPAD_DOWN,
+		DPAD_LEFT
+	};
+	constexpr std::array<PadButton, 15> PadButtons = {
+		PadButton::A,
+		PadButton::B,
+		PadButton::X,
+		PadButton::Y,
+		PadButton::BUMPER_LEFT,
+		PadButton::BUMPER_RIGHT,
+		PadButton::BACK,
+		PadButton::START,
+		PadButton::GUIDE,
+		PadButton::THUMB_LEFT,
+		PadButton::THUMB_RIGHT,
+		PadButton::DPAD_UP,
+		PadButton::DPAD_RIGHT,
+		PadButton::DPAD_DOWN,
+		PadButton::DPAD_LEFT
+	};
+	const std::array<const std::string, 15> PadButtonLabels = {
+		"A",
+		"B",
+		"X",
+		"Y",
+		"Left Bumper",
+		"Right Bumper",
+		"Back",
+		"Start",
+		"Guide",
+		"Left Thumb",
+		"Right Thumb",
+		"DPad Up",
+		"DPad Right",
+		"DPad Down",
+		"DPad left"
+	};
+
+	enum class PadAxis {
+		LEFT_X,
+		LEFT_Y,
+		RIGHT_X,
+		RIGHT_Y,
+		TRIGGER_LEFT,
+		TRIGGER_RIGHT
+	};
+	constexpr std::array<PadAxis,6> PadAxes = {
+		PadAxis::LEFT_X,
+		PadAxis::LEFT_Y,
+		PadAxis::RIGHT_X,
+		PadAxis::RIGHT_Y,
+		PadAxis::TRIGGER_LEFT,
+		PadAxis::TRIGGER_RIGHT
+	};
+	const std::array<const std::string, 15> PadAxisLabels = {
+		"Left (x)",
+		"Left (y)",
+		"Right (x)",
+		"Right (y)",
+		"Left Trigger",
+		"Right Trigger"
+	};
+
+	struct PadState {
+		std::bitset<PadButtons.size()> buttons;
+		float axes[PadAxes.size()];
+		bool present;
+	};
+
 	struct Joystick {
 		const char* name = nullptr;
 		const float* axes = nullptr;
@@ -31,6 +114,7 @@ namespace fggl::gfx {
 		int hatCount = 0;
 		int axisCount = 0;
 		int buttonCount = 0;
+		bool gamepad = false;
 	};
 
 	class Input {
@@ -84,7 +168,18 @@ namespace fggl::gfx {
 			void joystickConnect(int id, Joystick& data);
 			void joystickDisconnect(int id);
 
-			bool joystick(int id) const;
+			bool hasJoystick(int id) const;
+			const Joystick& joystick(int id) const;
+
+			// gamepads
+			void padState(int id, const PadState& state);
+
+			bool padDown(int id, PadButton btn);
+			bool padPressed(int id, PadButton btn);
+			bool padReleased(int id, PadButton btn);
+
+			float padAxis(int id, PadAxis axis);
+			float padAxisDelta(int id, PadAxis axis);
 
 		private:
 			Input();
@@ -92,6 +187,8 @@ namespace fggl::gfx {
 			MouseInputState m_mouse_last;
 			std::bitset<16> m_joysticks;
 			std::array<Joystick, 16> m_joydata;
+			std::array<PadState, 16> m_pad_curr;
+			std::array<PadState, 16> m_pad_last;
 	};
 
 };
diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp
index 9cde13b79954041e69d0b6b43abbca9ff2912a46..f1aa1b581ac9a063ed1ac3a475962ca65d43a878 100644
--- a/fggl/gfx/window.cpp
+++ b/fggl/gfx/window.cpp
@@ -38,16 +38,54 @@ static void fggl_input_mouse_btn(GLFWwindow* window, int btn, int action, int mo
 	input.mouseBtn( buttonBit, action == GLFW_PRESS );
 }
 
+static void fggl_activate_joystick(Input& input, int jid) {
+	fggl::gfx::Joystick data;
+	data.name = glfwGetJoystickName(jid);
+	data.axes = glfwGetJoystickAxes(jid, &data.axisCount);
+	data.buttons = glfwGetJoystickButtons(jid, &data.buttonCount);
+	data.hats = glfwGetJoystickHats(jid, &data.hatCount);
+	data.gamepad = glfwJoystickIsGamepad(jid);
+	input.joystickConnect(jid, data);
+
+	if ( data.gamepad ) {
+		GLFWgamepadstate state;
+		glfwGetGamepadState(jid, &state);
+
+		fggl::gfx::PadState padState;
+		for (int i=0; i<=GLFW_GAMEPAD_BUTTON_LAST; i++) {
+			padState.buttons[i] = state.buttons[i];
+		}
+
+		for (int i=0; i<=GLFW_GAMEPAD_AXIS_LAST; i++) {
+			padState.axes[i] = state.axes[i];
+		}
+
+		input.padState(jid, padState);
+	}
+}
+
+static void fggl_joystick_init() {
+	Input& input = Input::instance();
+	for (int jid=0; jid<16; jid++) {
+		if ( glfwJoystickPresent(jid) ) {
+			fggl_activate_joystick(input, jid);
+		}
+	}
+}
+
+static void fggl_joystick_poll() {
+	Input& input = Input::instance();
+	for (int jid=0; jid<16; jid++) {
+		if ( glfwJoystickPresent(jid) ) {
+			fggl_activate_joystick(input, jid);
+		}
+	}
+}
+
 static void fggl_joystick(int jid, int state) {
 	Input& input = Input::instance();
 	if ( state == GLFW_CONNECTED ) {
-		fggl::gfx::Joystick data;
-		data.name = glfwGetJoystickName(jid);
-		data.axes = glfwGetJoystickAxes(jid, &data.axisCount);
-		data.buttons = glfwGetJoystickButtons(jid, &data.buttonCount);
-		data.hats = glfwGetJoystickHats(jid, &data.hatCount);
-
-		input.joystickConnect(jid, data);
+		fggl_activate_joystick(input, jid);
 	} else if ( state == GLFW_DISCONNECTED ) {
 		input.joystickDisconnect(jid);
 	}
@@ -62,6 +100,10 @@ Context::Context() {
 		int code = glfwGetError(error);
 		throw std::runtime_error( *error );
 	}
+
+	// joysticks are global
+	fggl_joystick_init();
+	glfwSetJoystickCallback(fggl_joystick);
 }
 
 Context::~Context() {
@@ -70,6 +112,7 @@ Context::~Context() {
 
 void Context::pollEvents() {
 	glfwPollEvents();
+	fggl_joystick_poll();
 }
 
 Window::Window(Input& input) : m_window(nullptr), m_input(input) {
@@ -91,7 +134,6 @@ Window::Window(Input& input) : m_window(nullptr), m_input(input) {
 	glfwSetScrollCallback(m_window, fggl_input_scroll);
 	glfwSetCursorPosCallback(m_window, fggl_input_cursor);
 	glfwSetMouseButtonCallback(m_window, fggl_input_mouse_btn);
-	glfwSetJoystickCallback(fggl_joystick);
 }
 
 Window::~Window() {