commit 41a75f74226936f002811bd1568a1c1a5e9707f2
Author: Jasper <j.blanckenburg@fasttube.de>
Date:   Thu May 19 20:07:15 2022 +0200

    Initial commit

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..eb1167f
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,137 @@
+---
+Language: Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveMacros: false
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterCaseLabel: false
+  AfterClass: false
+  AfterControlStatement: false
+  AfterEnum: false
+  AfterFunction: false
+  AfterNamespace: false
+  AfterObjCDeclaration: false
+  AfterStruct: false
+  AfterUnion: false
+  AfterExternBlock: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: "^ IWYU pragma:"
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeBlocks: Regroup
+IncludeCategories:
+  - Regex: '^[<"](fmt|SDL2)/'
+    Priority: 3
+    SortPriority: 0
+  - Regex: "^<"
+    Priority: 4
+    SortPriority: 0
+  - Regex: ".*"
+    Priority: 1
+    SortPriority: 0
+IncludeIsMainRegex: "(Test)?$"
+IncludeIsMainSourceRegex: ""
+IndentCaseLabels: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ""
+MacroBlockEnd: ""
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+Standard: Latest
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+---
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..01f9cb9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+build/
+.vscode/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..f7dc952
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,60 @@
+cmake_minimum_required(VERSION 3.10)
+
+project("FT22 Steering Wheel Display")
+
+list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
+
+set(CMAKE_CXX_STANDARD 17)
+
+# DEPENDENCIES {{{
+
+find_package(SDL2 REQUIRED)
+find_package(SDL2TTF REQUIRED)
+find_package(SDL2IMAGE REQUIRED)
+
+include(FetchContent)
+
+# fmtlib
+set(stw_display_orig_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS})
+set(BUILD_SHARED_LIBS ON)
+FetchContent_Declare(
+    fmt
+    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
+    GIT_TAG 8.1.1
+)
+if (NOT fmt_POPULATED)
+    FetchContent_MakeAvailable(fmt)
+endif()
+if (NOT DEFINED fmt_INCLUDE_DIR)
+    set(fmt_INCLUDE_DIR ${fmt_SOURCE_DIR}/${FMT_INC_DIR})
+endif()
+set(BUILD_SHARED_LIBS ${lhotse_orig_BUILD_SHARED_LIBS})
+
+# }}}
+
+add_compile_options(-Wall -Wextra -Wimplicit-fallthrough)
+
+add_executable(
+    stw-display
+    src/main.cpp
+    src/App.cpp
+    src/AMI.cpp
+    src/widgets.cpp
+    src/util.cpp
+)
+target_include_directories(
+    stw-display PUBLIC
+    "include"
+)
+target_include_directories(
+    stw-display PRIVATE
+    ${SDL2_INCLUDE_DIRS}
+    ${fmt_INCLUDE_DIR}
+)
+target_link_libraries(
+    stw-display
+    ${SDL2_LIBRARIES}
+    ${SDL2TTF_LIBRARY}
+    ${SDL2IMAGE_LIBRARY}
+    fmt)
+add_dependencies(stw-display fmt)
\ No newline at end of file
diff --git a/cmake/FindSDL2IMAGE.cmake b/cmake/FindSDL2IMAGE.cmake
new file mode 100644
index 0000000..1656c01
--- /dev/null
+++ b/cmake/FindSDL2IMAGE.cmake
@@ -0,0 +1,163 @@
+# Locate SDL2 library
+# This module defines
+# SDL2_LIBRARY, the name of the library to link against
+# SDL2_FOUND, if false, do not try to link to SDL2
+# SDL2_INCLUDE_DIR, where to find SDL.h
+#
+# This module responds to the the flag:
+# SDL2_BUILDING_LIBRARY
+# If this is defined, then no SDL2main will be linked in because
+# only applications need main().
+# Otherwise, it is assumed you are building an application and this
+# module will attempt to locate and set the the proper link flags
+# as part of the returned SDL2_LIBRARY variable.
+#
+# Don't forget to include SDLmain.h and SDLmain.m your project for the
+# OS X framework based version. (Other versions link to -lSDL2main which
+# this module will try to find on your behalf.) Also for OS X, this
+# module will automatically add the -framework Cocoa on your behalf.
+#
+#
+# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
+# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
+# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
+# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
+# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
+# as appropriate. These values are used to generate the final SDL2_LIBRARY
+# variable, but when these values are unset, SDL2_LIBRARY does not get created.
+#
+#
+# $SDL2DIR is an environment variable that would
+# correspond to the ./configure --prefix=$SDL2DIR
+# used in building SDL2.
+# l.e.galup  9-20-02
+#
+# Modified by Eric Wing.
+# Added code to assist with automated building by using environmental variables
+# and providing a more controlled/consistent search behavior.
+# Added new modifications to recognize OS X frameworks and
+# additional Unix paths (FreeBSD, etc).
+# Also corrected the header search path to follow "proper" SDL guidelines.
+# Added a search for SDL2main which is needed by some platforms.
+# Added a search for threads which is needed by some platforms.
+# Added needed compile switches for MinGW.
+#
+# On OSX, this will prefer the Framework version (if found) over others.
+# People will have to manually change the cache values of
+# SDL2_LIBRARY to override this selection or set the CMake environment
+# CMAKE_INCLUDE_PATH to modify the search paths.
+#
+# Note that the header path has changed from SDL2/SDL.h to just SDL.h
+# This needed to change because "proper" SDL convention
+# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
+# reasons because not all systems place things in SDL2/ (see FreeBSD).
+
+#=============================================================================
+# Copyright 2003-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+#  License text for the above reference.)
+
+SET(SDL2IMAGE_SEARCH_PATHS
+	~/Library/Frameworks
+	/Library/Frameworks
+	/usr/local
+	/usr
+	/sw # Fink
+	/opt/local # DarwinPorts
+	/opt/csw # Blastwave
+	/opt
+)
+
+FIND_PATH(SDL2IMAGE_INCLUDE_DIR SDL_image.h
+	HINTS
+	$ENV{SDL2IMAGEDIR}
+	PATH_SUFFIXES include/SDL2 include
+	PATHS ${SDL2IMAGE_SEARCH_PATHS}
+)
+
+FIND_LIBRARY(SDL2IMAGE_LIBRARY_TEMP
+	NAMES SDL2_image
+	HINTS
+	$ENV{SDL2IMAGEDIR}
+	PATH_SUFFIXES lib64 lib
+	PATHS ${SDL2IMAGE_SEARCH_PATHS}
+)
+
+IF(NOT SDL2IMAGE_BUILDING_LIBRARY)
+	IF(NOT ${SDL2IMAGE_INCLUDE_DIR} MATCHES ".framework")
+		# Non-OS X framework versions expect you to also dynamically link to
+		# SDL2IMAGEmain. This is mainly for Windows and OS X. Other (Unix) platforms
+		# seem to provide SDL2IMAGEmain for compatibility even though they don't
+		# necessarily need it.
+		FIND_LIBRARY(SDL2IMAGEMAIN_LIBRARY
+			NAMES SDL2_image
+			HINTS
+			$ENV{SDL2IMAGEDIR}
+			PATH_SUFFIXES lib64 lib
+			PATHS ${SDL2IMAGE_SEARCH_PATHS}
+		)
+	ENDIF(NOT ${SDL2IMAGE_INCLUDE_DIR} MATCHES ".framework")
+ENDIF(NOT SDL2IMAGE_BUILDING_LIBRARY)
+
+# SDL2IMAGE may require threads on your system.
+# The Apple build may not need an explicit flag because one of the
+# frameworks may already provide it.
+# But for non-OSX systems, I will use the CMake Threads package.
+IF(NOT APPLE)
+	FIND_PACKAGE(Threads)
+ENDIF(NOT APPLE)
+
+# MinGW needs an additional library, mwindows
+# It's total link flags should look like -lmingw32 -lSDL2IMAGEmain -lSDL2IMAGE -lmwindows
+# (Actually on second look, I think it only needs one of the m* libraries.)
+IF(MINGW)
+	SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
+ENDIF(MINGW)
+
+IF(SDL2IMAGE_LIBRARY_TEMP)
+	# For SDL2IMAGEmain
+	IF(NOT SDL2IMAGE_BUILDING_LIBRARY)
+		IF(SDL2IMAGEMAIN_LIBRARY)
+			SET(SDL2IMAGE_LIBRARY_TEMP ${SDL2IMAGEMAIN_LIBRARY} ${SDL2IMAGE_LIBRARY_TEMP})
+		ENDIF(SDL2IMAGEMAIN_LIBRARY)
+	ENDIF(NOT SDL2IMAGE_BUILDING_LIBRARY)
+
+	# For OS X, SDL2IMAGE uses Cocoa as a backend so it must link to Cocoa.
+	# CMake doesn't display the -framework Cocoa string in the UI even
+	# though it actually is there if I modify a pre-used variable.
+	# I think it has something to do with the CACHE STRING.
+	# So I use a temporary variable until the end so I can set the
+	# "real" variable in one-shot.
+	IF(APPLE)
+		SET(SDL2IMAGE_LIBRARY_TEMP ${SDL2IMAGE_LIBRARY_TEMP} "-framework Cocoa")
+	ENDIF(APPLE)
+
+	# For threads, as mentioned Apple doesn't need this.
+	# In fact, there seems to be a problem if I used the Threads package
+	# and try using this line, so I'm just skipping it entirely for OS X.
+	IF(NOT APPLE)
+		SET(SDL2IMAGE_LIBRARY_TEMP ${SDL2IMAGE_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
+	ENDIF(NOT APPLE)
+
+	# For MinGW library
+	IF(MINGW)
+		SET(SDL2IMAGE_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2IMAGE_LIBRARY_TEMP})
+	ENDIF(MINGW)
+
+	# Set the final string here so the GUI reflects the final state.
+	SET(SDL2IMAGE_LIBRARY ${SDL2IMAGE_LIBRARY_TEMP} CACHE STRING "Where the SDL2IMAGE Library can be found")
+	# Set the temp variable to INTERNAL so it is not seen in the CMake GUI
+	SET(SDL2IMAGE_LIBRARY_TEMP "${SDL2IMAGE_LIBRARY_TEMP}" CACHE INTERNAL "")
+ENDIF(SDL2IMAGE_LIBRARY_TEMP)
+
+INCLUDE(FindPackageHandleStandardArgs)
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2IMAGE REQUIRED_VARS SDL2IMAGE_LIBRARY SDL2IMAGE_INCLUDE_DIR)
\ No newline at end of file
diff --git a/cmake/FindSDL2TTF.cmake b/cmake/FindSDL2TTF.cmake
new file mode 100644
index 0000000..be8e7bc
--- /dev/null
+++ b/cmake/FindSDL2TTF.cmake
@@ -0,0 +1,163 @@
+# Locate SDL2 library
+# This module defines
+# SDL2_LIBRARY, the name of the library to link against
+# SDL2_FOUND, if false, do not try to link to SDL2
+# SDL2_INCLUDE_DIR, where to find SDL.h
+#
+# This module responds to the the flag:
+# SDL2_BUILDING_LIBRARY
+# If this is defined, then no SDL2main will be linked in because
+# only applications need main().
+# Otherwise, it is assumed you are building an application and this
+# module will attempt to locate and set the the proper link flags
+# as part of the returned SDL2_LIBRARY variable.
+#
+# Don't forget to include SDLmain.h and SDLmain.m your project for the
+# OS X framework based version. (Other versions link to -lSDL2main which
+# this module will try to find on your behalf.) Also for OS X, this
+# module will automatically add the -framework Cocoa on your behalf.
+#
+#
+# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
+# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
+# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
+# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
+# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
+# as appropriate. These values are used to generate the final SDL2_LIBRARY
+# variable, but when these values are unset, SDL2_LIBRARY does not get created.
+#
+#
+# $SDL2DIR is an environment variable that would
+# correspond to the ./configure --prefix=$SDL2DIR
+# used in building SDL2.
+# l.e.galup  9-20-02
+#
+# Modified by Eric Wing.
+# Added code to assist with automated building by using environmental variables
+# and providing a more controlled/consistent search behavior.
+# Added new modifications to recognize OS X frameworks and
+# additional Unix paths (FreeBSD, etc).
+# Also corrected the header search path to follow "proper" SDL guidelines.
+# Added a search for SDL2main which is needed by some platforms.
+# Added a search for threads which is needed by some platforms.
+# Added needed compile switches for MinGW.
+#
+# On OSX, this will prefer the Framework version (if found) over others.
+# People will have to manually change the cache values of
+# SDL2_LIBRARY to override this selection or set the CMake environment
+# CMAKE_INCLUDE_PATH to modify the search paths.
+#
+# Note that the header path has changed from SDL2/SDL.h to just SDL.h
+# This needed to change because "proper" SDL convention
+# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
+# reasons because not all systems place things in SDL2/ (see FreeBSD).
+
+#=============================================================================
+# Copyright 2003-2009 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+#  License text for the above reference.)
+
+SET(SDL2TTF_SEARCH_PATHS
+	~/Library/Frameworks
+	/Library/Frameworks
+	/usr/local
+	/usr
+	/sw # Fink
+	/opt/local # DarwinPorts
+	/opt/csw # Blastwave
+	/opt
+)
+
+FIND_PATH(SDL2TTF_INCLUDE_DIR SDL_ttf.h
+	HINTS
+	$ENV{SDL2TTFDIR}
+	PATH_SUFFIXES include/SDL2 include
+	PATHS ${SDL2TTF_SEARCH_PATHS}
+)
+
+FIND_LIBRARY(SDL2TTF_LIBRARY_TEMP
+	NAMES SDL2_ttf
+	HINTS
+	$ENV{SDL2TTFDIR}
+	PATH_SUFFIXES lib64 lib
+	PATHS ${SDL2TTF_SEARCH_PATHS}
+)
+
+IF(NOT SDL2TTF_BUILDING_LIBRARY)
+	IF(NOT ${SDL2TTF_INCLUDE_DIR} MATCHES ".framework")
+		# Non-OS X framework versions expect you to also dynamically link to
+		# SDL2TTFmain. This is mainly for Windows and OS X. Other (Unix) platforms
+		# seem to provide SDL2TTFmain for compatibility even though they don't
+		# necessarily need it.
+		FIND_LIBRARY(SDL2TTFMAIN_LIBRARY
+			NAMES SDL2_ttf
+			HINTS
+			$ENV{SDL2TTFDIR}
+			PATH_SUFFIXES lib64 lib
+			PATHS ${SDL2TTF_SEARCH_PATHS}
+		)
+	ENDIF(NOT ${SDL2TTF_INCLUDE_DIR} MATCHES ".framework")
+ENDIF(NOT SDL2TTF_BUILDING_LIBRARY)
+
+# SDL2TTF may require threads on your system.
+# The Apple build may not need an explicit flag because one of the
+# frameworks may already provide it.
+# But for non-OSX systems, I will use the CMake Threads package.
+IF(NOT APPLE)
+	FIND_PACKAGE(Threads)
+ENDIF(NOT APPLE)
+
+# MinGW needs an additional library, mwindows
+# It's total link flags should look like -lmingw32 -lSDL2TTFmain -lSDL2TTF -lmwindows
+# (Actually on second look, I think it only needs one of the m* libraries.)
+IF(MINGW)
+	SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW")
+ENDIF(MINGW)
+
+IF(SDL2TTF_LIBRARY_TEMP)
+	# For SDL2TTFmain
+	IF(NOT SDL2TTF_BUILDING_LIBRARY)
+		IF(SDL2TTFMAIN_LIBRARY)
+			SET(SDL2TTF_LIBRARY_TEMP ${SDL2TTFMAIN_LIBRARY} ${SDL2TTF_LIBRARY_TEMP})
+		ENDIF(SDL2TTFMAIN_LIBRARY)
+	ENDIF(NOT SDL2TTF_BUILDING_LIBRARY)
+
+	# For OS X, SDL2TTF uses Cocoa as a backend so it must link to Cocoa.
+	# CMake doesn't display the -framework Cocoa string in the UI even
+	# though it actually is there if I modify a pre-used variable.
+	# I think it has something to do with the CACHE STRING.
+	# So I use a temporary variable until the end so I can set the
+	# "real" variable in one-shot.
+	IF(APPLE)
+		SET(SDL2TTF_LIBRARY_TEMP ${SDL2TTF_LIBRARY_TEMP} "-framework Cocoa")
+	ENDIF(APPLE)
+
+	# For threads, as mentioned Apple doesn't need this.
+	# In fact, there seems to be a problem if I used the Threads package
+	# and try using this line, so I'm just skipping it entirely for OS X.
+	IF(NOT APPLE)
+		SET(SDL2TTF_LIBRARY_TEMP ${SDL2TTF_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
+	ENDIF(NOT APPLE)
+
+	# For MinGW library
+	IF(MINGW)
+		SET(SDL2TTF_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2TTF_LIBRARY_TEMP})
+	ENDIF(MINGW)
+
+	# Set the final string here so the GUI reflects the final state.
+	SET(SDL2TTF_LIBRARY ${SDL2TTF_LIBRARY_TEMP} CACHE STRING "Where the SDL2TTF Library can be found")
+	# Set the temp variable to INTERNAL so it is not seen in the CMake GUI
+	SET(SDL2TTF_LIBRARY_TEMP "${SDL2TTF_LIBRARY_TEMP}" CACHE INTERNAL "")
+ENDIF(SDL2TTF_LIBRARY_TEMP)
+
+INCLUDE(FindPackageHandleStandardArgs)
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2TTF REQUIRED_VARS SDL2TTF_LIBRARY SDL2TTF_INCLUDE_DIR)
\ No newline at end of file
diff --git a/include/AMI.h b/include/AMI.h
new file mode 100644
index 0000000..02bab54
--- /dev/null
+++ b/include/AMI.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "widgets.h"
+
+#include <SDL2/SDL.h>
+
+constexpr const char* FT_LOGO_PATH = "resources/Fasttube_Logo-white.bmp";
+
+class AMI {
+public:
+  AMI(SDL_Renderer* renderer);
+
+  void draw();
+
+private:
+  SDL_Renderer* renderer;
+
+  ImageWidget ft_logo;
+  TextWidget choose;
+};
\ No newline at end of file
diff --git a/include/App.h b/include/App.h
new file mode 100644
index 0000000..6980a3d
--- /dev/null
+++ b/include/App.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "AMI.h"
+
+#include <SDL2/SDL.h>
+
+#include <memory>
+
+constexpr int SCREEN_WIDTH = 480;
+constexpr int SCREEN_HEIGHT = 320;
+
+enum class AppView { AMI, DRIVER, TESTING };
+
+class SDLManager {
+public:
+  SDLManager();
+  ~SDLManager();
+};
+
+class App {
+public:
+  App();
+  ~App();
+
+  int run();
+
+private:
+  void init_sdl();
+
+  void handle_events();
+  void render();
+
+  // The SDLManager initializes & quits SDL and its subsystems. Thus, it must be
+  // the first member of the class, so that its constructor is called before the
+  // others and its destructor is called after the others.
+  SDLManager sdl_manager;
+
+  std::unique_ptr<AMI> ami;
+
+  bool running;
+  AppView view;
+
+  SDL_Window* window;
+  SDL_Renderer* renderer;
+};
\ No newline at end of file
diff --git a/include/util.h b/include/util.h
new file mode 100644
index 0000000..c104e41
--- /dev/null
+++ b/include/util.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+
+#include <string>
+
+namespace util {
+
+SDL_Texture* load_img(SDL_Renderer* renderer, const std::string& path);
+TTF_Font* load_font(const std::string& path, int pt);
+
+}
\ No newline at end of file
diff --git a/include/widgets.h b/include/widgets.h
new file mode 100644
index 0000000..aa00708
--- /dev/null
+++ b/include/widgets.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+
+#include <optional>
+#include <string>
+
+class Widget {
+public:
+  Widget(SDL_Renderer* renderer, SDL_Rect dest_rect);
+
+  virtual void update_rect(SDL_Rect new_rect);
+
+  virtual void draw() = 0;
+
+protected:
+  SDL_Renderer* renderer;
+  SDL_Rect dest_rect;
+};
+
+class TextureWidget : public Widget {
+public:
+  TextureWidget(SDL_Renderer* renderer, SDL_Rect dest_rect,
+                std::optional<SDL_Texture*> texture);
+  ~TextureWidget();
+
+  virtual void draw() override;
+
+  void update_texture(SDL_Texture* new_texture);
+
+private:
+  std::optional<SDL_Texture*> texture;
+};
+
+class ImageWidget : public TextureWidget {
+public:
+  ImageWidget(SDL_Renderer* renderer, SDL_Rect dest_rect,
+              const std::string& path);
+};
+
+class TextWidget : public TextureWidget {
+public:
+  TextWidget(SDL_Renderer* renderer, SDL_Rect dest_rect,
+             const std::string& font_path,
+             const std::string& initial_text = "");
+  ~TextWidget();
+
+  void update_text(const std::string& new_text);
+
+protected:
+  TTF_Font* font;
+
+  std::string text;
+
+  SDL_Texture* generate_text(const std::string& text);
+};
\ No newline at end of file
diff --git a/resources/Avenir-Book.ttf b/resources/Avenir-Book.ttf
new file mode 100644
index 0000000..96da4d7
Binary files /dev/null and b/resources/Avenir-Book.ttf differ
diff --git a/resources/Fasttube_Logo-white.bmp b/resources/Fasttube_Logo-white.bmp
new file mode 100644
index 0000000..c18bb18
Binary files /dev/null and b/resources/Fasttube_Logo-white.bmp differ
diff --git a/resources/Fasttube_Logo-white.png b/resources/Fasttube_Logo-white.png
new file mode 100644
index 0000000..00dd85c
Binary files /dev/null and b/resources/Fasttube_Logo-white.png differ
diff --git a/src/AMI.cpp b/src/AMI.cpp
new file mode 100644
index 0000000..656e508
--- /dev/null
+++ b/src/AMI.cpp
@@ -0,0 +1,17 @@
+#include "AMI.h"
+
+AMI::AMI(SDL_Renderer* renderer)
+    : renderer{renderer}, ft_logo{renderer,
+                                  {.x = 182, .y = 0, .w = 116, .h = 40},
+                                  FT_LOGO_PATH},
+      choose{renderer,
+             {.x = 0, .y = 45, .w = 480, .h = 40},
+             "resources/Avenir-Book.ttf",
+             "Choose a Mission:"} {}
+
+void AMI::draw() {
+  SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
+  SDL_RenderClear(renderer);
+  ft_logo.draw();
+  choose.draw();
+}
\ No newline at end of file
diff --git a/src/App.cpp b/src/App.cpp
new file mode 100644
index 0000000..fe34268
--- /dev/null
+++ b/src/App.cpp
@@ -0,0 +1,89 @@
+#include "App.h"
+
+#include "AMI.h"
+
+#include <SDL2/SDL_image.h>
+#include <fmt/format.h>
+
+#include <stdexcept>
+
+App::App() : view{AppView::AMI} { init_sdl(); }
+
+App::~App() {
+  // Destroy window
+  SDL_DestroyRenderer(renderer);
+  renderer = nullptr;
+  SDL_DestroyWindow(window);
+  window = nullptr;
+}
+
+void App::init_sdl() {
+  window = SDL_CreateWindow("FT22 STW Display", SDL_WINDOWPOS_UNDEFINED,
+                            SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
+                            SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
+  if (window == nullptr) {
+    throw std::runtime_error(
+        fmt::format("Couldn't create window: {}", SDL_GetError()));
+  }
+  renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+  if (renderer == nullptr) {
+    throw std::runtime_error(
+        fmt::format("Couldn't create renderer: {}", SDL_GetError()));
+  }
+}
+
+int App::run() {
+  ami = std::make_unique<AMI>(renderer);
+
+  running = true;
+
+  while (running) {
+    handle_events();
+    render();
+    SDL_Delay(1);
+  }
+
+  return 0;
+}
+
+void App::handle_events() {
+  SDL_Event e;
+  while (SDL_PollEvent(&e) != 0) {
+    if (e.type == SDL_QUIT) {
+      running = false;
+    }
+  }
+}
+
+void App::render() {
+  switch (view) {
+  case AppView::AMI:
+    ami->draw();
+    break;
+  default:
+    throw std::runtime_error(fmt::format("Unknown view: {}", (int)view));
+  }
+  SDL_RenderPresent(renderer);
+}
+
+SDLManager::SDLManager() {
+  if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+    throw std::runtime_error(
+        fmt::format("Couldn't initialize SDL: {}", SDL_GetError()));
+  }
+  if (TTF_Init() == -1) {
+    throw std::runtime_error(
+        fmt::format("Couldn't initialize SDL_ttf: {}", TTF_GetError()));
+  }
+  int img_flags = IMG_INIT_PNG;
+  if (!(IMG_Init(img_flags) & img_flags)) {
+    throw std::runtime_error(
+        fmt::format("Couldn't initialitze SDL_image: {}", IMG_GetError()));
+  }
+}
+
+SDLManager::~SDLManager() {
+  IMG_Quit();
+  TTF_Quit();
+  SDL_Quit();
+}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..2b60725
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,6 @@
+#include "App.h"
+
+int main() {
+  App app;
+  return app.run();
+}
\ No newline at end of file
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..ed95a6c
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,34 @@
+#include "util.h"
+
+#include <SDL2/SDL_image.h>
+#include <fmt/format.h>
+
+#include <stdexcept>
+
+namespace util {
+
+SDL_Texture* load_img(SDL_Renderer* renderer, const std::string& path) {
+  SDL_Surface* loaded = IMG_Load(path.c_str());
+  if (loaded == nullptr) {
+    throw std::runtime_error(
+        fmt::format("Unable to load image {}: {}", path, IMG_GetError()));
+  }
+  SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, loaded);
+  if (texture == nullptr) {
+    throw std::runtime_error(fmt::format(
+        "Unable to convert image {} to texture: {}", path, IMG_GetError()));
+  }
+  SDL_FreeSurface(loaded);
+  return texture;
+}
+
+TTF_Font* load_font(const std::string& path, int pt) {
+  TTF_Font* font = TTF_OpenFont(path.c_str(), pt);
+  if (font == nullptr) {
+    throw std::runtime_error(
+        fmt::format("Unable to load font {}: {}", path, TTF_GetError()));
+  }
+  return font;
+}
+
+} // namespace util
diff --git a/src/widgets.cpp b/src/widgets.cpp
new file mode 100644
index 0000000..27f61af
--- /dev/null
+++ b/src/widgets.cpp
@@ -0,0 +1,75 @@
+#include "widgets.h"
+
+#include "util.h"
+
+#include <SDL2/SDL_image.h>
+#include <fmt/format.h>
+
+#include <stdexcept>
+
+Widget::Widget(SDL_Renderer* renderer, SDL_Rect rect)
+    : renderer{renderer}, dest_rect{rect} {}
+
+void Widget::update_rect(SDL_Rect new_rect) { dest_rect = new_rect; }
+
+TextureWidget::TextureWidget(SDL_Renderer* renderer, SDL_Rect dest_rect,
+                             std::optional<SDL_Texture*> texture)
+    : Widget{renderer, dest_rect}, texture{texture} {}
+
+TextureWidget::~TextureWidget() {
+  if (texture) {
+    SDL_DestroyTexture(*texture);
+    texture = nullptr;
+  }
+}
+
+void TextureWidget::draw() {
+  if (texture) {
+    SDL_RenderCopy(renderer, *texture, NULL, &dest_rect);
+  }
+}
+
+void TextureWidget::update_texture(SDL_Texture* new_texture) {
+  if (texture) {
+    SDL_DestroyTexture(*texture);
+  }
+  texture = new_texture;
+}
+
+ImageWidget::ImageWidget(SDL_Renderer* renderer, SDL_Rect dest_rect,
+                         const std::string& path)
+    : TextureWidget{renderer, dest_rect, util::load_img(renderer, path)} {}
+
+TextWidget::TextWidget(SDL_Renderer* renderer, SDL_Rect dest_rect,
+                       const std::string& font_path,
+                       const std::string& initial_text)
+    : TextureWidget{renderer, dest_rect, std::nullopt},
+      font{util::load_font(font_path, 28)}, text{initial_text} {
+  if (text != "") {
+    update_texture(generate_text(text));
+  }
+}
+
+TextWidget::~TextWidget() { TTF_CloseFont(font); }
+
+void TextWidget::update_text(const std::string& new_text) {
+  if (text != new_text) {
+    update_texture(generate_text(new_text));
+    text = new_text;
+  }
+}
+
+SDL_Texture* TextWidget::generate_text(const std::string& text) {
+  SDL_Surface* surf =
+      TTF_RenderText_Solid(font, text.c_str(), {0xFF, 0xFF, 0XFF, 0xFF});
+  if (surf == nullptr) {
+    throw std::runtime_error(
+        fmt::format("Unable to render text surface: {}", TTF_GetError()));
+  }
+  SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surf);
+  if (texture == nullptr) {
+    throw std::runtime_error(fmt::format(
+        "Unable to create texture from text surface: {}", SDL_GetError()));
+  }
+  return texture;
+}
\ No newline at end of file