Compare commits
9 Commits
882a645926
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a97a07da0 | |||
| 1e378d1093 | |||
| 938978f5d4 | |||
| 0cd0ce6f3e | |||
| c553ea36d4 | |||
| 49f703f4d4 | |||
| 8affda125e | |||
| 2e76523bf4 | |||
| 654c71f3ee |
@ -68,7 +68,14 @@ ForEachMacros:
|
|||||||
- BOOST_FOREACH
|
- BOOST_FOREACH
|
||||||
IncludeBlocks: Regroup
|
IncludeBlocks: Regroup
|
||||||
IncludeCategories:
|
IncludeCategories:
|
||||||
- Regex: '^[<"](fmt|SDL2)/'
|
# This will hopefully only catch stdlib headers
|
||||||
|
- Regex: "^<[^/]*>"
|
||||||
|
Priority: 5
|
||||||
|
SortPriority: 0
|
||||||
|
- Regex: '^[<"]SDL2/'
|
||||||
|
Priority: 2
|
||||||
|
SortPriority: 0
|
||||||
|
- Regex: '^[<"](fmt|spdlog)/'
|
||||||
Priority: 3
|
Priority: 3
|
||||||
SortPriority: 0
|
SortPriority: 0
|
||||||
- Regex: "^<"
|
- Regex: "^<"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project("FT22 Steering Wheel Display")
|
project("FT22 Steering Wheel Display")
|
||||||
|
|
||||||
@ -11,6 +11,12 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(SDL2TTF REQUIRED)
|
find_package(SDL2TTF REQUIRED)
|
||||||
find_package(SDL2IMAGE REQUIRED)
|
find_package(SDL2IMAGE REQUIRED)
|
||||||
|
find_library(I2C_LIBRARY i2c REQUIRED)
|
||||||
|
if ("${I2C_LIBRARY}" STREQUAL "I2C_LIBRARY-NOTFOUND")
|
||||||
|
message(FATAL_ERROR "libi2c not found!")
|
||||||
|
else()
|
||||||
|
message("libi2c found at ${I2C_LIBRARY}")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
@ -28,7 +34,17 @@ endif()
|
|||||||
if (NOT DEFINED fmt_INCLUDE_DIR)
|
if (NOT DEFINED fmt_INCLUDE_DIR)
|
||||||
set(fmt_INCLUDE_DIR ${fmt_SOURCE_DIR}/${FMT_INC_DIR})
|
set(fmt_INCLUDE_DIR ${fmt_SOURCE_DIR}/${FMT_INC_DIR})
|
||||||
endif()
|
endif()
|
||||||
set(BUILD_SHARED_LIBS ${lhotse_orig_BUILD_SHARED_LIBS})
|
set(BUILD_SHARED_LIBS ${stw_display_orig_BUILD_SHARED_LIBS})
|
||||||
|
|
||||||
|
# spdlog
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE true)
|
||||||
|
set(SPDLOG_FMT_EXTERNAL_HO true)
|
||||||
|
FetchContent_Declare(
|
||||||
|
spdlog
|
||||||
|
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
||||||
|
GIT_TAG v1.9.2
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(spdlog)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@ -38,10 +54,15 @@ add_executable(
|
|||||||
stw-display
|
stw-display
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/App.cpp
|
src/App.cpp
|
||||||
|
src/AppState.cpp
|
||||||
src/View.cpp
|
src/View.cpp
|
||||||
|
src/MissionSelect.cpp
|
||||||
src/AMI.cpp
|
src/AMI.cpp
|
||||||
src/widgets.cpp
|
src/widgets.cpp
|
||||||
src/util.cpp
|
src/util.cpp
|
||||||
|
src/DriverView.cpp
|
||||||
|
src/BaseDataSource.cpp
|
||||||
|
src/DemoDataSource.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
stw-display PUBLIC
|
stw-display PUBLIC
|
||||||
@ -57,5 +78,7 @@ target_link_libraries(
|
|||||||
${SDL2_LIBRARIES}
|
${SDL2_LIBRARIES}
|
||||||
${SDL2TTF_LIBRARY}
|
${SDL2TTF_LIBRARY}
|
||||||
${SDL2IMAGE_LIBRARY}
|
${SDL2IMAGE_LIBRARY}
|
||||||
fmt)
|
${I2C_LIBRARY}
|
||||||
add_dependencies(stw-display fmt)
|
fmt
|
||||||
|
spdlog::spdlog)
|
||||||
|
add_dependencies(stw-display fmt)
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "AppState.h"
|
||||||
#include "View.h"
|
#include "View.h"
|
||||||
#include "widgets.h"
|
#include "widgets.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL_render.h>
|
||||||
|
#include <SDL2/SDL_ttf.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class AMI final : public View {
|
class AMI final : public View {
|
||||||
@ -13,16 +16,12 @@ public:
|
|||||||
AMI(SDL_Renderer* renderer);
|
AMI(SDL_Renderer* renderer);
|
||||||
~AMI();
|
~AMI();
|
||||||
|
|
||||||
void draw() override;
|
void draw(const AppState& state) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TTF_Font* avenir;
|
TTF_Font* font_medium;
|
||||||
TTF_Font* chinat;
|
TTF_Font* font_large;
|
||||||
|
|
||||||
std::vector<Widget*> widgets;
|
std::unique_ptr<TextWidget> header;
|
||||||
|
std::unordered_map<Mission, std::unique_ptr<TextWidget>> missions;
|
||||||
std::unique_ptr<ImageWidget> ft_logo;
|
|
||||||
std::unique_ptr<TextWidget> choose;
|
|
||||||
std::unique_ptr<ListWidget> missions_widget;
|
|
||||||
std::vector<std::unique_ptr<Widget>> missions;
|
|
||||||
};
|
};
|
||||||
@ -1,20 +1,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AMI.h"
|
#include "AMI.h"
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "DriverView.h"
|
||||||
|
#include "MissionSelect.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
enum class AppView { AMI, DRIVER, TESTING };
|
|
||||||
|
|
||||||
class SDLManager {
|
class SDLManager {
|
||||||
public:
|
public:
|
||||||
SDLManager();
|
SDLManager();
|
||||||
~SDLManager();
|
~SDLManager();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
class KeyboardHandler {
|
||||||
|
public:
|
||||||
|
void handle_keyup(const SDL_Event& ev, AppState& state);
|
||||||
|
};
|
||||||
|
#endif // !NDEBUG
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public:
|
public:
|
||||||
App();
|
App();
|
||||||
@ -24,6 +32,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void init_sdl();
|
void init_sdl();
|
||||||
|
void init_state();
|
||||||
|
|
||||||
void handle_events();
|
void handle_events();
|
||||||
void render();
|
void render();
|
||||||
@ -33,11 +42,17 @@ private:
|
|||||||
// others and its destructor is called after the others.
|
// others and its destructor is called after the others.
|
||||||
SDLManager sdl_manager;
|
SDLManager sdl_manager;
|
||||||
|
|
||||||
|
std::unique_ptr<AppState> state;
|
||||||
|
std::unique_ptr<MissionSelect> mission_select;
|
||||||
std::unique_ptr<AMI> ami;
|
std::unique_ptr<AMI> ami;
|
||||||
|
std::unique_ptr<DriverView> driver_view;
|
||||||
|
|
||||||
bool running;
|
bool running;
|
||||||
AppView view;
|
|
||||||
|
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
SDL_Renderer* renderer;
|
SDL_Renderer* renderer;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
KeyboardHandler keyboard_handler;
|
||||||
|
#endif // !NDEBUG
|
||||||
};
|
};
|
||||||
55
include/AppState.h
Normal file
55
include/AppState.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "DemoDataSource.h"
|
||||||
|
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
constexpr int I2C_SLAVE_ADDR = 0x20;
|
||||||
|
constexpr int I2C_POLL_COMMAND = 0x00;
|
||||||
|
|
||||||
|
enum class AppView { MISSION_SELECT, AMI, DRIVER, TESTING };
|
||||||
|
std::ostream& operator<<(std::ostream& os, AppView view);
|
||||||
|
enum class Mission {
|
||||||
|
NONE,
|
||||||
|
ACCELERATION,
|
||||||
|
SKIDPAD,
|
||||||
|
AUTOCROSS,
|
||||||
|
TRACKDRIVE,
|
||||||
|
EBS_TEST,
|
||||||
|
INSPECTION,
|
||||||
|
MANUAL
|
||||||
|
};
|
||||||
|
std::ostream& operator<<(std::ostream& os, Mission mission);
|
||||||
|
|
||||||
|
class AppState {
|
||||||
|
public:
|
||||||
|
AppState(const std::string& i2c_dev_path);
|
||||||
|
~AppState();
|
||||||
|
|
||||||
|
void poll();
|
||||||
|
|
||||||
|
AppView get_view() const;
|
||||||
|
Mission get_mission() const;
|
||||||
|
DemoDataSource get_data_source() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void unmarshal_mission_select(uint8_t* data);
|
||||||
|
void unmarshal_ami(uint8_t* data);
|
||||||
|
|
||||||
|
// This is optional so that we can still run the code on a PC without I2C
|
||||||
|
std::optional<int> i2c_dev_file;
|
||||||
|
|
||||||
|
AppView view;
|
||||||
|
Mission mission;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
friend class KeyboardHandler;
|
||||||
|
DemoDataSource data_source;
|
||||||
|
#endif // !NDEBUG
|
||||||
|
};
|
||||||
|
|
||||||
|
extern AppState app_state;
|
||||||
34
include/BaseDataSource.h
Normal file
34
include/BaseDataSource.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class BaseDataSource {
|
||||||
|
public:
|
||||||
|
double get_speed(); // in km/h
|
||||||
|
double get_brake_balance(); // 0.0 - 1.0
|
||||||
|
double get_hv_voltage(); // in V
|
||||||
|
double get_hv_voltage_ratio(); // 0.0 - 1.0
|
||||||
|
double get_lv_voltage(); // in V
|
||||||
|
double get_battery_temperature(); // in °C
|
||||||
|
double get_throttle_ratio(); // 0.0 - 1.0
|
||||||
|
double get_brake_pressure_front_bar(); // in bar
|
||||||
|
double get_brake_pressure_rear_bar(); // in bar
|
||||||
|
|
||||||
|
bool brake_balance_change_is_recent(); // true if the last change was less
|
||||||
|
// than 1 second ago
|
||||||
|
|
||||||
|
virtual void poll() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void set_brake_balance(double value);
|
||||||
|
|
||||||
|
double speed;
|
||||||
|
double brake_balance;
|
||||||
|
double hv_voltage;
|
||||||
|
double hv_voltage_ratio;
|
||||||
|
double lv_voltage;
|
||||||
|
double battery_temperature;
|
||||||
|
double throttle_ratio;
|
||||||
|
double brake_pressure_front_bar;
|
||||||
|
double brake_pressure_rear_bar;
|
||||||
|
|
||||||
|
int time_of_last_brake_balance_change;
|
||||||
|
};
|
||||||
13
include/DemoDataSource.h
Normal file
13
include/DemoDataSource.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseDataSource.h"
|
||||||
|
|
||||||
|
class DemoDataSource final : public BaseDataSource {
|
||||||
|
public:
|
||||||
|
DemoDataSource();
|
||||||
|
~DemoDataSource();
|
||||||
|
|
||||||
|
void poll() override;
|
||||||
|
void bump_brake_balance_up();
|
||||||
|
void bump_brake_balance_down();
|
||||||
|
};
|
||||||
30
include/DriverView.h
Normal file
30
include/DriverView.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "View.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "widgets.h"
|
||||||
|
|
||||||
|
class DriverView final : public View {
|
||||||
|
public:
|
||||||
|
DriverView(SDL_Renderer* renderer);
|
||||||
|
~DriverView();
|
||||||
|
|
||||||
|
void draw(const AppState& state) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TTF_Font* font_large;
|
||||||
|
TTF_Font* font_detail;
|
||||||
|
TTF_Font* font_tiny;
|
||||||
|
TTF_Font* font_giant;
|
||||||
|
TTF_Font* font_medium;
|
||||||
|
std::unique_ptr<TextWidget> speed_widget;
|
||||||
|
std::unique_ptr<TextWidget> speed_hint_widget;
|
||||||
|
|
||||||
|
std::unique_ptr<TextWidget> brake_balance_widget;
|
||||||
|
std::unique_ptr<TextWidget> brake_balance_hint_widget;
|
||||||
|
|
||||||
|
std::unique_ptr<TextWidget> general_info_widget;
|
||||||
|
|
||||||
|
std::unique_ptr<TextWidget> focus_widget;
|
||||||
|
std::unique_ptr<TextWidget> focus_hint_widget;
|
||||||
|
};
|
||||||
31
include/MissionSelect.h
Normal file
31
include/MissionSelect.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "View.h"
|
||||||
|
#include "events.h"
|
||||||
|
#include "widgets.h"
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class MissionSelect final : public View {
|
||||||
|
public:
|
||||||
|
MissionSelect(SDL_Renderer* renderer);
|
||||||
|
~MissionSelect();
|
||||||
|
|
||||||
|
void draw(const AppState& state) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TTF_Font* avenir;
|
||||||
|
TTF_Font* chinat;
|
||||||
|
|
||||||
|
std::vector<Widget*> widgets;
|
||||||
|
|
||||||
|
std::unique_ptr<ImageWidget> ft_logo;
|
||||||
|
std::unique_ptr<TextWidget> choose;
|
||||||
|
std::unique_ptr<ListWidget> missions_widget;
|
||||||
|
std::vector<std::unique_ptr<Widget>> missions;
|
||||||
|
};
|
||||||
@ -1,13 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "events.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
class View {
|
class View {
|
||||||
public:
|
public:
|
||||||
View(SDL_Renderer* renderer);
|
View(SDL_Renderer* renderer);
|
||||||
virtual ~View();
|
virtual ~View();
|
||||||
|
|
||||||
virtual void draw() = 0;
|
virtual void draw(const AppState& state) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SDL_Renderer* renderer;
|
SDL_Renderer* renderer;
|
||||||
|
|||||||
3
include/events.h
Normal file
3
include/events.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
enum class Event { Next, Prev, Confirm };
|
||||||
@ -3,17 +3,30 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_ttf.h>
|
#include <SDL2/SDL_ttf.h>
|
||||||
|
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <ostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum class Alignment { LEFT, RIGHT, CENTER };
|
enum class Alignment {
|
||||||
|
START,
|
||||||
|
END,
|
||||||
|
CENTER,
|
||||||
|
LEFT = START,
|
||||||
|
TOP = START,
|
||||||
|
RIGHT = END,
|
||||||
|
BOTTOM = END
|
||||||
|
};
|
||||||
|
std::ostream& operator<<(std::ostream& os, Alignment align);
|
||||||
struct PositionInfo {
|
struct PositionInfo {
|
||||||
PositionInfo();
|
PositionInfo();
|
||||||
|
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
Alignment align;
|
Alignment align_h;
|
||||||
|
Alignment align_v;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Widget {
|
class Widget {
|
||||||
@ -24,7 +37,7 @@ public:
|
|||||||
virtual void set_width(int width, bool preserve_aspect_ratio = true);
|
virtual void set_width(int width, bool preserve_aspect_ratio = true);
|
||||||
virtual void set_height(int height, bool preserve_aspect_ratio = true);
|
virtual void set_height(int height, bool preserve_aspect_ratio = true);
|
||||||
virtual void set_position(int x, int y);
|
virtual void set_position(int x, int y);
|
||||||
virtual void set_alignment(Alignment align);
|
virtual void set_alignment(Alignment align_h, Alignment align_v);
|
||||||
|
|
||||||
int get_width();
|
int get_width();
|
||||||
int get_height();
|
int get_height();
|
||||||
@ -66,12 +79,13 @@ public:
|
|||||||
~TextWidget();
|
~TextWidget();
|
||||||
|
|
||||||
void update_text(const std::string& new_text);
|
void update_text(const std::string& new_text);
|
||||||
|
void set_wrap_width(int width);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
TTF_Font* font;
|
TTF_Font* font;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
int wrap_width = -1; // -1 means no wrapping (default behavior)
|
||||||
SDL_Texture* generate_text(const std::string& text);
|
SDL_Texture* generate_text(const std::string& text);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,10 +99,17 @@ public:
|
|||||||
|
|
||||||
virtual void draw() override;
|
virtual void draw() override;
|
||||||
|
|
||||||
|
void select_next();
|
||||||
|
void select_prev();
|
||||||
|
void select(size_t n);
|
||||||
|
size_t get_selection();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int element_height;
|
int element_height;
|
||||||
Alignment element_alignment;
|
Alignment element_alignment;
|
||||||
|
|
||||||
|
size_t selection;
|
||||||
|
|
||||||
std::vector<Widget*> elements;
|
std::vector<Widget*> elements;
|
||||||
|
|
||||||
void place_element(Widget* element, int index);
|
void place_element(Widget* element, int index);
|
||||||
|
|||||||
87
src/AMI.cpp
87
src/AMI.cpp
@ -1,65 +1,64 @@
|
|||||||
#include "AMI.h"
|
#include "AMI.h"
|
||||||
|
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "SDL_render.h"
|
||||||
|
#include "SDL_ttf.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "widgets.h"
|
#include "widgets.h"
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
constexpr const char* FT_LOGO_PATH = "resources/Fasttube_Logo-white.bmp";
|
|
||||||
constexpr const char* AVENIR_FONT_PATH = "resources/Avenir-Book.ttf";
|
|
||||||
constexpr int AVENIR_PTS = 20;
|
|
||||||
constexpr const char* CHINAT_FONT_PATH = "resources/CHINAT.ttf";
|
constexpr const char* CHINAT_FONT_PATH = "resources/CHINAT.ttf";
|
||||||
constexpr int CHINAT_PTS = 24;
|
constexpr int CHINAT_MEDIUM_PTS = 25;
|
||||||
constexpr int FT_LOGO_HEIGHT = 40;
|
constexpr int CHINAT_LARGE_PTS = 40;
|
||||||
constexpr int GAP = 5;
|
constexpr int HEADER_Y = 10;
|
||||||
constexpr int CHOOSE_Y = FT_LOGO_HEIGHT + GAP;
|
|
||||||
|
|
||||||
constexpr std::array MISSIONS = {"ACCELERATION", "SKIDPAD", "AUTOCROSS",
|
|
||||||
"TRACKDRIVE", "EBS TEST", "INSPECTION",
|
|
||||||
"MANUAL DRIVING"};
|
|
||||||
|
|
||||||
AMI::AMI(SDL_Renderer* renderer)
|
AMI::AMI(SDL_Renderer* renderer)
|
||||||
: View{renderer}, avenir{util::load_font(AVENIR_FONT_PATH, AVENIR_PTS)},
|
: View{renderer}, font_medium{util::load_font(CHINAT_FONT_PATH,
|
||||||
chinat{util::load_font(CHINAT_FONT_PATH, CHINAT_PTS)} {
|
CHINAT_MEDIUM_PTS)},
|
||||||
ft_logo = std::make_unique<ImageWidget>(renderer, FT_LOGO_PATH);
|
font_large{util::load_font(CHINAT_FONT_PATH, CHINAT_LARGE_PTS)} {
|
||||||
ft_logo->set_height(FT_LOGO_HEIGHT);
|
header =
|
||||||
ft_logo->set_position(SCREEN_WIDTH / 2, 0);
|
std::make_unique<TextWidget>(renderer, font_medium, "Current mission:");
|
||||||
ft_logo->set_alignment(Alignment::CENTER);
|
header->set_position(SCREEN_WIDTH / 2, HEADER_Y);
|
||||||
widgets.push_back(ft_logo.get());
|
header->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
|
||||||
choose = std::make_unique<TextWidget>(renderer, avenir, "Choose a mission:");
|
missions.emplace(Mission::NONE,
|
||||||
choose->set_position(SCREEN_WIDTH / 2, CHOOSE_Y);
|
std::make_unique<TextWidget>(renderer, font_large,
|
||||||
choose->set_alignment(Alignment::CENTER);
|
"NO MISSION SELECTED"));
|
||||||
widgets.push_back(choose.get());
|
missions.emplace(
|
||||||
|
Mission::ACCELERATION,
|
||||||
for (const auto mission : MISSIONS) {
|
std::make_unique<TextWidget>(renderer, font_large, "ACCELERATION"));
|
||||||
missions.push_back(std::make_unique<TextWidget>(renderer, chinat, mission));
|
missions.emplace(Mission::SKIDPAD, std::make_unique<TextWidget>(
|
||||||
|
renderer, font_large, "SKIDPAD"));
|
||||||
|
missions.emplace(Mission::AUTOCROSS, std::make_unique<TextWidget>(
|
||||||
|
renderer, font_large, "AUTOCROSS"));
|
||||||
|
missions.emplace(
|
||||||
|
Mission::TRACKDRIVE,
|
||||||
|
std::make_unique<TextWidget>(renderer, font_large, "TRACKDRIVE"));
|
||||||
|
missions.emplace(Mission::EBS_TEST, std::make_unique<TextWidget>(
|
||||||
|
renderer, font_large, "EBS TEST"));
|
||||||
|
missions.emplace(
|
||||||
|
Mission::INSPECTION,
|
||||||
|
std::make_unique<TextWidget>(renderer, font_large, "INSPECTION"));
|
||||||
|
missions.emplace(
|
||||||
|
Mission::MANUAL,
|
||||||
|
std::make_unique<TextWidget>(renderer, font_large, "MANUAL DRIVING"));
|
||||||
|
for (auto& [mission, widget] : missions) {
|
||||||
|
widget->set_position(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
|
||||||
|
widget->set_alignment(Alignment::CENTER, Alignment::CENTER);
|
||||||
}
|
}
|
||||||
int choose_height = choose->get_height();
|
|
||||||
int missions_y = CHOOSE_Y + choose_height + GAP;
|
|
||||||
int missions_height = SCREEN_HEIGHT - CHOOSE_Y - choose_height - 1;
|
|
||||||
missions_widget = std::make_unique<ListWidget>(
|
|
||||||
renderer, missions[0]->get_height(), Alignment::CENTER);
|
|
||||||
missions_widget->set_position(0, missions_y);
|
|
||||||
missions_widget->set_width(SCREEN_WIDTH, false);
|
|
||||||
missions_widget->set_height(missions_height, false);
|
|
||||||
for (const auto& mission : missions) {
|
|
||||||
missions_widget->add_element(mission.get());
|
|
||||||
}
|
|
||||||
widgets.push_back(missions_widget.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AMI::~AMI() {
|
AMI::~AMI() {
|
||||||
TTF_CloseFont(avenir);
|
TTF_CloseFont(font_medium);
|
||||||
TTF_CloseFont(chinat);
|
TTF_CloseFont(font_large);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AMI::draw() {
|
void AMI::draw(const AppState& state) {
|
||||||
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
for (const auto& widget : widgets) {
|
|
||||||
widget->draw();
|
header->draw();
|
||||||
}
|
missions[state.get_mission()]->draw();
|
||||||
}
|
}
|
||||||
121
src/App.cpp
121
src/App.cpp
@ -1,14 +1,31 @@
|
|||||||
#include "App.h"
|
#include "App.h"
|
||||||
|
|
||||||
#include "AMI.h"
|
#include "AMI.h"
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "DemoDataSource.h"
|
||||||
|
#include "DriverView.h"
|
||||||
|
#include "MissionSelect.h"
|
||||||
|
#include "events.h"
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_events.h>
|
||||||
#include <SDL2/SDL_image.h>
|
#include <SDL2/SDL_image.h>
|
||||||
#include <fmt/format.h>
|
#include <SDL2/SDL_keycode.h>
|
||||||
|
#include <SDL2/SDL_render.h>
|
||||||
|
#include <SDL2/SDL_timer.h>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
App::App() : view{AppView::AMI} { init_sdl(); }
|
App::App() {
|
||||||
|
init_sdl();
|
||||||
|
init_state();
|
||||||
|
}
|
||||||
|
|
||||||
App::~App() {
|
App::~App() {
|
||||||
// Destroy window
|
// Destroy window
|
||||||
@ -26,21 +43,52 @@ void App::init_sdl() {
|
|||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
fmt::format("Couldn't create window: {}", SDL_GetError()));
|
fmt::format("Couldn't create window: {}", SDL_GetError()));
|
||||||
}
|
}
|
||||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
// Software renderer is *MUCH* (~50x speedup) faster than hardware accelerated
|
||||||
|
// renderer on a Pi Zero.
|
||||||
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
|
||||||
if (renderer == nullptr) {
|
if (renderer == nullptr) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
fmt::format("Couldn't create renderer: {}", SDL_GetError()));
|
fmt::format("Couldn't create renderer: {}", SDL_GetError()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void App::init_state() {
|
||||||
|
try {
|
||||||
|
state = std::make_unique<AppState>("/dev/i2c-3");
|
||||||
|
} catch (const std::runtime_error& e) {
|
||||||
|
spdlog::error("Couldn't create AppState: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int App::run() {
|
int App::run() {
|
||||||
|
mission_select = std::make_unique<MissionSelect>(renderer);
|
||||||
ami = std::make_unique<AMI>(renderer);
|
ami = std::make_unique<AMI>(renderer);
|
||||||
|
driver_view = std::make_unique<DriverView>(renderer);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
std::cout << "Change brake balance with W (+) and S (-)" << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
|
|
||||||
|
uint32_t second_start = SDL_GetTicks();
|
||||||
|
unsigned frames = 0;
|
||||||
while (running) {
|
while (running) {
|
||||||
|
uint32_t now = SDL_GetTicks();
|
||||||
|
if (now - second_start > 5000) {
|
||||||
|
spdlog::info("{} FPS", frames / 5);
|
||||||
|
second_start = now;
|
||||||
|
frames = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != nullptr) {
|
||||||
|
state->poll();
|
||||||
|
}
|
||||||
|
|
||||||
handle_events();
|
handle_events();
|
||||||
render();
|
render();
|
||||||
|
|
||||||
|
frames++;
|
||||||
SDL_Delay(1);
|
SDL_Delay(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,21 +96,33 @@ int App::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void App::handle_events() {
|
void App::handle_events() {
|
||||||
|
std::queue<Event> events;
|
||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
while (SDL_PollEvent(&e) != 0) {
|
while (SDL_PollEvent(&e) != 0) {
|
||||||
if (e.type == SDL_QUIT) {
|
if (e.type == SDL_QUIT) {
|
||||||
running = false;
|
running = false;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
} else if (e.type == SDL_KEYUP) {
|
||||||
|
keyboard_handler.handle_keyup(e, *state);
|
||||||
|
#endif // !NDEBUG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::render() {
|
void App::render() {
|
||||||
|
AppView view = state->get_view();
|
||||||
switch (view) {
|
switch (view) {
|
||||||
|
case AppView::MISSION_SELECT:
|
||||||
|
mission_select->draw(*state);
|
||||||
|
break;
|
||||||
case AppView::AMI:
|
case AppView::AMI:
|
||||||
ami->draw();
|
ami->draw(*state);
|
||||||
|
break;
|
||||||
|
case AppView::DRIVER:
|
||||||
|
driver_view->draw(*state);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error(fmt::format("Unknown view: {}", (int)view));
|
throw std::runtime_error(fmt::format("Unknown view: {}", view));
|
||||||
}
|
}
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
}
|
}
|
||||||
@ -87,4 +147,53 @@ SDLManager::~SDLManager() {
|
|||||||
IMG_Quit();
|
IMG_Quit();
|
||||||
TTF_Quit();
|
TTF_Quit();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle key presses
|
||||||
|
*/
|
||||||
|
void KeyboardHandler::handle_keyup(const SDL_Event& ev, AppState& state) {
|
||||||
|
auto pressed_key = ev.key.keysym.sym;
|
||||||
|
|
||||||
|
// we are in the first screen (mission select)
|
||||||
|
if (state.view == AppView::MISSION_SELECT) {
|
||||||
|
|
||||||
|
// handle change selected mission
|
||||||
|
if (pressed_key == SDLK_DOWN || pressed_key == SDLK_RIGHT) {
|
||||||
|
auto new_mission = static_cast<int>(state.mission) + 1;
|
||||||
|
if (new_mission > 7) {
|
||||||
|
new_mission = 1;
|
||||||
|
}
|
||||||
|
state.mission = static_cast<Mission>(new_mission);
|
||||||
|
} else if (pressed_key == SDLK_UP || pressed_key == SDLK_LEFT) {
|
||||||
|
auto new_mission = static_cast<int>(state.mission) - 1;
|
||||||
|
if (new_mission < 1) {
|
||||||
|
new_mission = 7;
|
||||||
|
}
|
||||||
|
state.mission = static_cast<Mission>(new_mission);
|
||||||
|
|
||||||
|
// commit mission selection
|
||||||
|
} else if (pressed_key == SDLK_RETURN) {
|
||||||
|
|
||||||
|
if (state.mission == Mission::MANUAL) {
|
||||||
|
state.view = AppView::DRIVER;
|
||||||
|
} else {
|
||||||
|
state.view = AppView::AMI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// go back to mission select
|
||||||
|
if (state.view == AppView::AMI || state.view == AppView::DRIVER) {
|
||||||
|
if (pressed_key == SDLK_ESCAPE) {
|
||||||
|
state.view = AppView::MISSION_SELECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle mock brake balance change
|
||||||
|
if (pressed_key == SDLK_w) {
|
||||||
|
state.data_source.bump_brake_balance_up();
|
||||||
|
} else if (pressed_key == SDLK_s) {
|
||||||
|
state.data_source.bump_brake_balance_down();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
118
src/AppState.cpp
Normal file
118
src/AppState.cpp
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#include "AppState.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <optional>
|
||||||
|
extern "C" {
|
||||||
|
#include <i2c/smbus.h>
|
||||||
|
}
|
||||||
|
#include <linux/i2c-dev.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, AppView view) {
|
||||||
|
switch (view) {
|
||||||
|
case AppView::MISSION_SELECT:
|
||||||
|
return os << "MISSION_SELECT";
|
||||||
|
case AppView::AMI:
|
||||||
|
return os << "AMI";
|
||||||
|
case AppView::DRIVER:
|
||||||
|
return os << "DRIVER";
|
||||||
|
case AppView::TESTING:
|
||||||
|
return os << "TESTING";
|
||||||
|
default:
|
||||||
|
return os << "UNKNOWN_VIEW[" << static_cast<int>(view) << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Mission mission) {
|
||||||
|
switch (mission) {
|
||||||
|
case Mission::NONE:
|
||||||
|
return os << "NONE";
|
||||||
|
case Mission::ACCELERATION:
|
||||||
|
return os << "ACCELERATION";
|
||||||
|
case Mission::SKIDPAD:
|
||||||
|
return os << "SKIDPAD";
|
||||||
|
case Mission::AUTOCROSS:
|
||||||
|
return os << "AUTOCROSS";
|
||||||
|
case Mission::TRACKDRIVE:
|
||||||
|
return os << "TRACKDRIVE";
|
||||||
|
case Mission::EBS_TEST:
|
||||||
|
return os << "EBS_TEST";
|
||||||
|
case Mission::INSPECTION:
|
||||||
|
return os << "INSPECTION";
|
||||||
|
case Mission::MANUAL:
|
||||||
|
return os << "MANUAL";
|
||||||
|
default:
|
||||||
|
return os << "UNKNOWN_MISSION[" << static_cast<int>(mission) << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState::AppState(const std::string& i2c_dev_path)
|
||||||
|
: view{AppView::MISSION_SELECT}, mission{Mission::ACCELERATION} {
|
||||||
|
i2c_dev_file = open(i2c_dev_path.c_str(), O_RDWR);
|
||||||
|
if (i2c_dev_file < 0) {
|
||||||
|
spdlog::error("Couldn't open I2C device: {}", strerror(errno));
|
||||||
|
i2c_dev_file = std::nullopt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl(*i2c_dev_file, I2C_SLAVE, I2C_SLAVE_ADDR) < 0) {
|
||||||
|
spdlog::error("Couldn't set I2C slave address: {}", strerror(errno));
|
||||||
|
close(*i2c_dev_file);
|
||||||
|
i2c_dev_file = std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppState::~AppState() {
|
||||||
|
if (i2c_dev_file) {
|
||||||
|
close(*i2c_dev_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppState::poll() {
|
||||||
|
data_source.poll();
|
||||||
|
if (!i2c_dev_file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data[32];
|
||||||
|
int count = i2c_smbus_read_block_data(*i2c_dev_file, I2C_POLL_COMMAND, data);
|
||||||
|
if (count < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
fmt::format("Couldn't poll I2C slave: {}", strerror(errno)));
|
||||||
|
}
|
||||||
|
view = static_cast<AppView>(data[0]);
|
||||||
|
spdlog::info("View after poll: {}", view);
|
||||||
|
switch (view) {
|
||||||
|
case AppView::MISSION_SELECT:
|
||||||
|
unmarshal_mission_select(data);
|
||||||
|
break;
|
||||||
|
case AppView::AMI:
|
||||||
|
unmarshal_ami(data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error(fmt::format("Unknown view: {}", view));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppView AppState::get_view() const { return view; }
|
||||||
|
|
||||||
|
Mission AppState::get_mission() const { return mission; }
|
||||||
|
|
||||||
|
void AppState::unmarshal_mission_select(uint8_t* data) {
|
||||||
|
mission = static_cast<Mission>(data[1]);
|
||||||
|
spdlog::info("Mission after poll: {}", mission);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppState::unmarshal_ami(uint8_t* data) {
|
||||||
|
mission = static_cast<Mission>(data[1]);
|
||||||
|
spdlog::info("Mission after poll: {}", mission);
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoDataSource AppState::get_data_source() const { return data_source; }
|
||||||
35
src/BaseDataSource.cpp
Normal file
35
src/BaseDataSource.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "BaseDataSource.h"
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
double BaseDataSource::get_speed() { return speed; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_brake_balance() { return brake_balance; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_hv_voltage() { return hv_voltage; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_hv_voltage_ratio() { return hv_voltage_ratio; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_lv_voltage() { return lv_voltage; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_battery_temperature() { return battery_temperature; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_throttle_ratio() { return throttle_ratio; }
|
||||||
|
|
||||||
|
double BaseDataSource::get_brake_pressure_front_bar() {
|
||||||
|
return brake_pressure_front_bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
double BaseDataSource::get_brake_pressure_rear_bar() {
|
||||||
|
return brake_pressure_rear_bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseDataSource::set_brake_balance(double value) {
|
||||||
|
brake_balance = value;
|
||||||
|
time_of_last_brake_balance_change = SDL_GetTicks();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BaseDataSource::brake_balance_change_is_recent() {
|
||||||
|
|
||||||
|
return time_of_last_brake_balance_change != 0 &&
|
||||||
|
SDL_GetTicks() - time_of_last_brake_balance_change < 500;
|
||||||
|
}
|
||||||
33
src/DemoDataSource.cpp
Normal file
33
src/DemoDataSource.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "DemoDataSource.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
DemoDataSource::DemoDataSource() {
|
||||||
|
brake_balance = 0.5;
|
||||||
|
|
||||||
|
lv_voltage = 13.1;
|
||||||
|
battery_temperature = 20.1;
|
||||||
|
throttle_ratio = 0.01;
|
||||||
|
brake_pressure_front_bar = 1;
|
||||||
|
brake_pressure_rear_bar = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoDataSource::~DemoDataSource() {}
|
||||||
|
|
||||||
|
void DemoDataSource::poll() {
|
||||||
|
int min_voltage = 150;
|
||||||
|
int max_voltage = 480;
|
||||||
|
static int count = 0;
|
||||||
|
count++;
|
||||||
|
hv_voltage = 220.1 + std::cos(count / 1000.0 + 2) * 50;
|
||||||
|
hv_voltage_ratio = (hv_voltage - min_voltage) / (max_voltage - min_voltage);
|
||||||
|
speed = 10 + std::sin(count / 100.0) * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DemoDataSource::bump_brake_balance_up() {
|
||||||
|
set_brake_balance(brake_balance + 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DemoDataSource::bump_brake_balance_down() {
|
||||||
|
set_brake_balance(brake_balance - 0.01);
|
||||||
|
}
|
||||||
131
src/DriverView.cpp
Normal file
131
src/DriverView.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#include "DriverView.h"
|
||||||
|
|
||||||
|
#include "DemoDataSource.h"
|
||||||
|
#include "defines.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define LARGE_FONT_SIZE_POINTS 170
|
||||||
|
#define GIANT_FONT_SIZE_POINTS 250
|
||||||
|
#define MEDIUM_FONT_SIZE_POINTS 50
|
||||||
|
#define SOC_BAR_HEIGHT 80
|
||||||
|
#define SPEED_TEXT_X 22
|
||||||
|
#define SPEED_TEXT_Y 50
|
||||||
|
|
||||||
|
DriverView::DriverView(SDL_Renderer* renderer)
|
||||||
|
: View{renderer}, font_large{util::load_font("resources/Avenir-Book.ttf",
|
||||||
|
LARGE_FONT_SIZE_POINTS)},
|
||||||
|
font_detail{util::load_font("resources/Avenir-Book.ttf", 20)},
|
||||||
|
font_tiny{util::load_font("resources/Avenir-Book.ttf", 15)},
|
||||||
|
font_giant{
|
||||||
|
util::load_font("resources/Avenir-Book.ttf", GIANT_FONT_SIZE_POINTS)},
|
||||||
|
font_medium{util::load_font("resources/Avenir-Book.ttf",
|
||||||
|
MEDIUM_FONT_SIZE_POINTS)} {
|
||||||
|
speed_widget = std::make_unique<TextWidget>(renderer, font_large, "");
|
||||||
|
speed_widget->set_position(SPEED_TEXT_X, SPEED_TEXT_Y);
|
||||||
|
speed_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
|
||||||
|
speed_hint_widget = std::make_unique<TextWidget>(renderer, font_tiny, "SPD");
|
||||||
|
speed_hint_widget->set_position(20, 210);
|
||||||
|
speed_hint_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
|
||||||
|
brake_balance_widget = std::make_unique<TextWidget>(renderer, font_large, "");
|
||||||
|
brake_balance_widget->set_position(SPEED_TEXT_X + SCREEN_WIDTH / 2,
|
||||||
|
SPEED_TEXT_Y);
|
||||||
|
brake_balance_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
brake_balance_hint_widget =
|
||||||
|
std::make_unique<TextWidget>(renderer, font_tiny, "BB");
|
||||||
|
brake_balance_hint_widget->set_position(20 + SCREEN_WIDTH / 2, 210);
|
||||||
|
brake_balance_hint_widget->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
|
||||||
|
general_info_widget = std::make_unique<TextWidget>(renderer, font_detail, "");
|
||||||
|
general_info_widget->set_position(SPEED_TEXT_X, SPEED_TEXT_Y + 200);
|
||||||
|
general_info_widget->set_alignment(Alignment::LEFT, Alignment::TOP);
|
||||||
|
general_info_widget->set_wrap_width(SCREEN_WIDTH - 50);
|
||||||
|
|
||||||
|
focus_widget = std::make_unique<TextWidget>(renderer, font_giant, "");
|
||||||
|
focus_widget->set_position(120, 50);
|
||||||
|
focus_widget->set_alignment(Alignment::LEFT, Alignment::TOP);
|
||||||
|
focus_hint_widget = std::make_unique<TextWidget>(renderer, font_medium, "");
|
||||||
|
focus_hint_widget->set_position(120, 30);
|
||||||
|
// focus_hint_widget->set_alignment(Alignment::CENTER, Alignment::CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
DriverView::~DriverView() {
|
||||||
|
|
||||||
|
TTF_CloseFont(font_medium);
|
||||||
|
TTF_CloseFont(font_large);
|
||||||
|
TTF_CloseFont(font_detail);
|
||||||
|
TTF_CloseFont(font_tiny);
|
||||||
|
TTF_CloseFont(font_giant);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DriverView::draw(const AppState& state) {
|
||||||
|
auto ds = state.get_data_source();
|
||||||
|
auto hv_ratio = ds.get_hv_voltage_ratio();
|
||||||
|
SDL_Rect rect;
|
||||||
|
rect.x = 0;
|
||||||
|
rect.y = 0;
|
||||||
|
rect.w = SCREEN_WIDTH;
|
||||||
|
rect.h = SCREEN_HEIGHT;
|
||||||
|
|
||||||
|
static auto hv_low = false;
|
||||||
|
// hysteresis for the hv voltage low
|
||||||
|
if (hv_ratio < 0.15) {
|
||||||
|
hv_low = true;
|
||||||
|
} else if (hv_ratio > 0.2) {
|
||||||
|
hv_low = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int red = hv_low ? 0xa0 : 0x30;
|
||||||
|
SDL_SetRenderDrawColor(renderer, red, 0x30, 0x30, 255);
|
||||||
|
SDL_RenderFillRect(renderer, &rect);
|
||||||
|
|
||||||
|
auto brake_balance_text = fmt::format("{:.0f}", ds.get_brake_balance() * 100);
|
||||||
|
|
||||||
|
if (ds.brake_balance_change_is_recent()) {
|
||||||
|
SDL_SetRenderDrawColor(renderer, 255, 69, 100, 255);
|
||||||
|
SDL_RenderFillRect(renderer, &rect);
|
||||||
|
focus_widget->update_text(brake_balance_text);
|
||||||
|
focus_widget->draw();
|
||||||
|
focus_hint_widget->update_text("BBAL");
|
||||||
|
focus_hint_widget->draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.w = SCREEN_WIDTH * hv_ratio;
|
||||||
|
rect.h = SOC_BAR_HEIGHT;
|
||||||
|
SDL_SetRenderDrawColor(renderer, 76, 255, 0, 255);
|
||||||
|
SDL_RenderFillRect(renderer, &rect);
|
||||||
|
|
||||||
|
speed_widget->update_text(fmt::format("{:02.0f}", ds.get_speed()));
|
||||||
|
speed_widget->draw();
|
||||||
|
speed_hint_widget->draw();
|
||||||
|
|
||||||
|
brake_balance_widget->update_text(brake_balance_text);
|
||||||
|
|
||||||
|
brake_balance_widget->draw();
|
||||||
|
brake_balance_hint_widget->draw();
|
||||||
|
|
||||||
|
// draw rectangles around the speed and brake balance widgets
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 255);
|
||||||
|
rect.x = 0;
|
||||||
|
rect.y = SOC_BAR_HEIGHT;
|
||||||
|
rect.w = SCREEN_WIDTH / 2;
|
||||||
|
rect.h = 150;
|
||||||
|
SDL_RenderDrawRect(renderer, &rect);
|
||||||
|
rect.x = SCREEN_WIDTH / 2;
|
||||||
|
SDL_RenderDrawRect(renderer, &rect);
|
||||||
|
|
||||||
|
auto general_string = fmt::format(
|
||||||
|
"HV: {:.0f}V | LV: {:.1f}V | B_TEMP: {:.1f}C\nAPPS: {:.0f}% | BPF: "
|
||||||
|
"{:.0f} bar | BPR: {:.0f} bar",
|
||||||
|
ds.get_hv_voltage(), ds.get_lv_voltage(), ds.get_battery_temperature(),
|
||||||
|
ds.get_throttle_ratio() * 100, ds.get_brake_pressure_front_bar(),
|
||||||
|
ds.get_brake_pressure_rear_bar());
|
||||||
|
|
||||||
|
general_info_widget->update_text(general_string);
|
||||||
|
general_info_widget->draw();
|
||||||
|
}
|
||||||
74
src/MissionSelect.cpp
Normal file
74
src/MissionSelect.cpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include "MissionSelect.h"
|
||||||
|
|
||||||
|
#include "AppState.h"
|
||||||
|
#include "DriverView.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include "events.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "widgets.h"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
constexpr const char* FT_LOGO_PATH = "resources/Fasttube_Logo-white.bmp";
|
||||||
|
constexpr const char* AVENIR_FONT_PATH = "resources/Avenir-Book.ttf";
|
||||||
|
constexpr int AVENIR_PTS = 20;
|
||||||
|
constexpr const char* CHINAT_FONT_PATH = "resources/CHINAT.ttf";
|
||||||
|
constexpr int CHINAT_PTS = 24;
|
||||||
|
constexpr int FT_LOGO_HEIGHT = 40;
|
||||||
|
constexpr int GAP = 5;
|
||||||
|
constexpr int CHOOSE_Y = FT_LOGO_HEIGHT + GAP;
|
||||||
|
|
||||||
|
constexpr std::array MISSIONS = {"ACCELERATION", "SKIDPAD", "AUTOCROSS",
|
||||||
|
"TRACKDRIVE", "EBS TEST", "INSPECTION",
|
||||||
|
"MANUAL DRIVING"};
|
||||||
|
|
||||||
|
MissionSelect::MissionSelect(SDL_Renderer* renderer)
|
||||||
|
: View{renderer}, avenir{util::load_font(AVENIR_FONT_PATH, AVENIR_PTS)},
|
||||||
|
chinat{util::load_font(CHINAT_FONT_PATH, CHINAT_PTS)} {
|
||||||
|
ft_logo = std::make_unique<ImageWidget>(renderer, FT_LOGO_PATH);
|
||||||
|
ft_logo->set_height(FT_LOGO_HEIGHT);
|
||||||
|
ft_logo->set_position(SCREEN_WIDTH / 2, 0);
|
||||||
|
ft_logo->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
widgets.push_back(ft_logo.get());
|
||||||
|
|
||||||
|
choose = std::make_unique<TextWidget>(renderer, avenir, "Choose a mission:");
|
||||||
|
choose->set_position(SCREEN_WIDTH / 2, CHOOSE_Y);
|
||||||
|
choose->set_alignment(Alignment::CENTER, Alignment::TOP);
|
||||||
|
widgets.push_back(choose.get());
|
||||||
|
|
||||||
|
for (const auto mission : MISSIONS) {
|
||||||
|
missions.push_back(std::make_unique<TextWidget>(renderer, chinat, mission));
|
||||||
|
}
|
||||||
|
int choose_height = choose->get_height();
|
||||||
|
int missions_y = CHOOSE_Y + choose_height + GAP;
|
||||||
|
int missions_height = SCREEN_HEIGHT - CHOOSE_Y - choose_height - 1;
|
||||||
|
missions_widget = std::make_unique<ListWidget>(
|
||||||
|
renderer, missions[0]->get_height(), Alignment::CENTER);
|
||||||
|
missions_widget->set_position(0, missions_y);
|
||||||
|
missions_widget->set_width(SCREEN_WIDTH, false);
|
||||||
|
missions_widget->set_height(missions_height, false);
|
||||||
|
for (const auto& mission : missions) {
|
||||||
|
missions_widget->add_element(mission.get());
|
||||||
|
}
|
||||||
|
widgets.push_back(missions_widget.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
MissionSelect::~MissionSelect() {
|
||||||
|
TTF_CloseFont(avenir);
|
||||||
|
TTF_CloseFont(chinat);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MissionSelect::draw(const AppState& state) {
|
||||||
|
size_t n = static_cast<int>(state.get_mission()) - 1;
|
||||||
|
missions_widget->select(n);
|
||||||
|
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
for (const auto& widget : widgets) {
|
||||||
|
widget->draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/widgets.cpp
121
src/widgets.cpp
@ -5,11 +5,37 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_image.h>
|
#include <SDL2/SDL_image.h>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
PositionInfo::PositionInfo() : x{0}, y{0}, align{Alignment::LEFT} {}
|
constexpr uint8_t LIST_SELECTION_BG_R = 0x77;
|
||||||
|
constexpr uint8_t LIST_SELECTION_BG_G = 0x33;
|
||||||
|
constexpr uint8_t LIST_SELECTION_BG_B = 0x33;
|
||||||
|
constexpr uint8_t LIST_OTHER_BG_R = 0x22;
|
||||||
|
constexpr uint8_t LIST_OTHER_BG_G = 0x22;
|
||||||
|
constexpr uint8_t LIST_OTHER_BG_B = 0x22;
|
||||||
|
constexpr uint8_t LIST_NORMAL_BG_R = 0x00;
|
||||||
|
constexpr uint8_t LIST_NORMAL_BG_G = 0x00;
|
||||||
|
constexpr uint8_t LIST_NORMAL_BG_B = 0x00;
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, Alignment align) {
|
||||||
|
switch (align) {
|
||||||
|
case Alignment::START:
|
||||||
|
return os << "START";
|
||||||
|
case Alignment::CENTER:
|
||||||
|
return os << "CENTER";
|
||||||
|
case Alignment::END:
|
||||||
|
return os << "END";
|
||||||
|
default:
|
||||||
|
return os << "UNKNOWN ALIGNMENT[" << static_cast<int>(align) << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionInfo::PositionInfo()
|
||||||
|
: x{0}, y{0}, align_h{Alignment::LEFT}, align_v{Alignment::LEFT} {}
|
||||||
|
|
||||||
Widget::Widget(SDL_Renderer* renderer) : renderer{renderer}, rect{0, 0, 0, 0} {}
|
Widget::Widget(SDL_Renderer* renderer) : renderer{renderer}, rect{0, 0, 0, 0} {}
|
||||||
|
|
||||||
@ -39,8 +65,9 @@ void Widget::set_position(int x, int y) {
|
|||||||
recalculate_pos();
|
recalculate_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Widget::set_alignment(Alignment align) {
|
void Widget::set_alignment(Alignment align_h, Alignment align_v) {
|
||||||
pos.align = align;
|
pos.align_h = align_h;
|
||||||
|
pos.align_v = align_v;
|
||||||
recalculate_pos();
|
recalculate_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,19 +80,32 @@ const PositionInfo& Widget::get_position() { return pos; }
|
|||||||
void Widget::recalculate_pos() {
|
void Widget::recalculate_pos() {
|
||||||
int x = pos.x;
|
int x = pos.x;
|
||||||
int y = pos.y;
|
int y = pos.y;
|
||||||
switch (pos.align) {
|
|
||||||
case Alignment::LEFT:
|
switch (pos.align_h) {
|
||||||
|
case Alignment::START:
|
||||||
break;
|
break;
|
||||||
case Alignment::RIGHT:
|
case Alignment::END:
|
||||||
x -= rect.w;
|
x -= rect.w;
|
||||||
break;
|
break;
|
||||||
case Alignment::CENTER:
|
case Alignment::CENTER:
|
||||||
x -= rect.w / 2;
|
x -= rect.w / 2;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(fmt::format("Unknown alignment: {}", pos.align_h));
|
||||||
fmt::format("Unknown alignment: {}", (int)pos.align));
|
|
||||||
}
|
}
|
||||||
|
switch (pos.align_v) {
|
||||||
|
case Alignment::START:
|
||||||
|
break;
|
||||||
|
case Alignment::END:
|
||||||
|
y -= rect.h;
|
||||||
|
break;
|
||||||
|
case Alignment::CENTER:
|
||||||
|
y -= rect.h / 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error(fmt::format("Unknown alignment: {}", pos.align_v));
|
||||||
|
}
|
||||||
|
|
||||||
rect.x = x;
|
rect.x = x;
|
||||||
rect.y = y;
|
rect.y = y;
|
||||||
}
|
}
|
||||||
@ -127,8 +167,13 @@ void TextWidget::update_text(const std::string& new_text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_Texture* TextWidget::generate_text(const std::string& text) {
|
SDL_Texture* TextWidget::generate_text(const std::string& text) {
|
||||||
SDL_Surface* surf =
|
SDL_Surface* surf;
|
||||||
TTF_RenderText_Solid(font, text.c_str(), {0xFF, 0xFF, 0XFF, 0xFF});
|
if (wrap_width == -1) {
|
||||||
|
surf = TTF_RenderText_Blended(font, text.c_str(), {0xFF, 0xFF, 0XFF, 0xFF});
|
||||||
|
} else {
|
||||||
|
surf = TTF_RenderText_Blended_Wrapped(font, text.c_str(),
|
||||||
|
{0xFF, 0xFF, 0XFF, 0xFF}, wrap_width);
|
||||||
|
}
|
||||||
if (surf == nullptr) {
|
if (surf == nullptr) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
fmt::format("Unable to render text surface: {}", TTF_GetError()));
|
fmt::format("Unable to render text surface: {}", TTF_GetError()));
|
||||||
@ -141,10 +186,12 @@ SDL_Texture* TextWidget::generate_text(const std::string& text) {
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextWidget::set_wrap_width(int width) { wrap_width = width; }
|
||||||
|
|
||||||
ListWidget::ListWidget(SDL_Renderer* renderer, int element_height,
|
ListWidget::ListWidget(SDL_Renderer* renderer, int element_height,
|
||||||
Alignment element_alignment)
|
Alignment element_alignment)
|
||||||
: Widget{renderer}, element_height{element_height},
|
: Widget{renderer}, element_height{element_height},
|
||||||
element_alignment{element_alignment} {}
|
element_alignment{element_alignment}, selection{0} {}
|
||||||
|
|
||||||
ListWidget::~ListWidget() {}
|
ListWidget::~ListWidget() {}
|
||||||
|
|
||||||
@ -163,12 +210,19 @@ void ListWidget::draw() {
|
|||||||
// Since the elements are sorted, we can stop the loop now.
|
// Since the elements are sorted, we can stop the loop now.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (i % 2 == 1) {
|
if (i == selection) {
|
||||||
SDL_SetRenderDrawColor(renderer, 0x11, 0x11, 0x11, 0xFF);
|
SDL_SetRenderDrawColor(renderer, LIST_SELECTION_BG_R, LIST_SELECTION_BG_G,
|
||||||
SDL_Rect fill_rect = {
|
LIST_SELECTION_BG_B, 0xFF);
|
||||||
.x = pos.x, .y = element_pos.y, .w = rect.w, .h = element_height};
|
} else if (i % 2 == 1) {
|
||||||
SDL_RenderFillRect(renderer, &fill_rect);
|
SDL_SetRenderDrawColor(renderer, LIST_OTHER_BG_R, LIST_OTHER_BG_G,
|
||||||
|
LIST_OTHER_BG_B, 0xFF);
|
||||||
|
} else {
|
||||||
|
SDL_SetRenderDrawColor(renderer, LIST_NORMAL_BG_R, LIST_NORMAL_BG_G,
|
||||||
|
LIST_NORMAL_BG_B, 0xFF);
|
||||||
}
|
}
|
||||||
|
SDL_Rect fill_rect = {
|
||||||
|
.x = pos.x, .y = element_pos.y, .w = rect.w, .h = element_height};
|
||||||
|
SDL_RenderFillRect(renderer, &fill_rect);
|
||||||
element->draw();
|
element->draw();
|
||||||
if (i != elements.size() - 1) {
|
if (i != elements.size() - 1) {
|
||||||
int border_y = element_pos.y + element->get_height();
|
int border_y = element_pos.y + element->get_height();
|
||||||
@ -178,6 +232,37 @@ void ListWidget::draw() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ListWidget::select_next() {
|
||||||
|
size_t n = elements.size();
|
||||||
|
if (n == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selection = (selection + 1) % n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListWidget::select_prev() {
|
||||||
|
size_t n = elements.size();
|
||||||
|
if (n == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selection == 0) {
|
||||||
|
selection = n - 1;
|
||||||
|
} else {
|
||||||
|
selection = selection - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ListWidget::select(size_t n) {
|
||||||
|
if (n > elements.size()) {
|
||||||
|
throw std::runtime_error(fmt::format(
|
||||||
|
"Tried to select element {}, but there are only {} elements!", n,
|
||||||
|
elements.size()));
|
||||||
|
}
|
||||||
|
selection = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ListWidget::get_selection() { return selection; }
|
||||||
|
|
||||||
void ListWidget::place_element(Widget* element, int index) {
|
void ListWidget::place_element(Widget* element, int index) {
|
||||||
int x = rect.x;
|
int x = rect.x;
|
||||||
switch (element_alignment) {
|
switch (element_alignment) {
|
||||||
@ -191,11 +276,11 @@ void ListWidget::place_element(Widget* element, int index) {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
fmt::format("Unknown alignment: {}", (int)pos.align));
|
fmt::format("Unknown alignment: {}", element_alignment));
|
||||||
}
|
}
|
||||||
// Additional pixel for border
|
// Additional pixel for border
|
||||||
int y = rect.y + index * (element_height + 1);
|
int y = rect.y + index * (element_height + 1);
|
||||||
element->set_position(x, y);
|
element->set_position(x, y);
|
||||||
element->set_alignment(element_alignment);
|
element->set_alignment(element_alignment, Alignment::TOP);
|
||||||
element->set_height(element_height);
|
element->set_height(element_height);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user