6#include <linux/input.h>
17#define NK_INCLUDE_FIXED_TYPES
18#define NK_INCLUDE_STANDARD_IO
19#define NK_INCLUDE_STANDARD_VARARGS
20#define NK_INCLUDE_DEFAULT_ALLOCATOR
21#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
22#define NK_INCLUDE_FONT_BAKING
23#define NK_INCLUDE_DEFAULT_FONT
35inline constexpr unsigned K_FB_REQUIRED_BITS_PER_PIXEL = 32;
38inline constexpr std::uint32_t K_DEFAULT_FRAME_TIME_MS = 16;
44struct TouchCalibration {
57[[nodiscard]]
auto detect_touch_calibration(
const std::string& device_path)
79 [[nodiscard]]
auto contains(
const Point point)
const noexcept ->
bool {
80 return point.x >= x && point.y >= y && point.x < x + w && point.y < y + h;
86struct FramebufferView {
87 std::uint32_t* pixels{
nullptr};
93 FramebufferView() =
default;
96 FramebufferView(
const FramebufferView&) =
delete;
97 auto operator=(
const FramebufferView&) -> FramebufferView& =
delete;
100 FramebufferView(FramebufferView&& other) noexcept
101 : pixels(other.pixels),
103 height(other.height),
104 stride(other.stride),
105 fb_desc(other.fb_desc) {
106 other.pixels =
nullptr;
107 other.width = other.height = other.stride = 0;
111 auto operator=(FramebufferView&& other)
noexcept -> FramebufferView& {
112 if (
this != &other) {
114 if (pixels !=
nullptr && fb_desc >= 0) {
120 pixels = other.pixels;
122 height = other.height;
123 stride = other.stride;
124 fb_desc = other.fb_desc;
126 other.pixels =
nullptr;
127 other.width = other.height = other.stride = 0;
135 explicit FramebufferView(
const std::string& path) {
136 const char* fbdev = path.c_str();
137 fb_desc = open(fbdev, O_RDWR);
143 fb_var_screeninfo vinfo{};
144 if (ioctl(fb_desc, FBIOGET_VSCREENINFO, &vinfo) < 0) {
145 perror(
"FBIOGET_VSCREENINFO");
152 vinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
153 if (ioctl(fb_desc, FBIOPUT_VSCREENINFO, &vinfo) < 0) {
154 perror(
"FBIOGET_VSCREENINFO");
158 fb_fix_screeninfo finfo{};
159 if (ioctl(fb_desc, FBIOGET_FSCREENINFO, &finfo) < 0) {
160 perror(
"FBIOGET_FSCREENINFO");
164 if (vinfo.bits_per_pixel != K_FB_REQUIRED_BITS_PER_PIXEL) {
165 fprintf(stderr,
"Need 32bpp (ARGB8888), got %u\n", vinfo.bits_per_pixel);
169 width =
static_cast<int>(vinfo.xres);
170 height =
static_cast<int>(vinfo.yres);
172 stride =
static_cast<int>(finfo.line_length / 4);
174 const size_t screensize =
static_cast<size_t>(finfo.line_length) *
175 static_cast<size_t>(vinfo.yres);
176 void*
const fbp = mmap(
nullptr, screensize, PROT_READ | PROT_WRITE,
177 MAP_SHARED, fb_desc, 0);
178 if (fbp == MAP_FAILED) {
181 width = height = stride = 0;
185 pixels =
static_cast<std::uint32_t*
>(fbp);
187 explicit FramebufferView(
const char* path)
188 : FramebufferView(std::string(path)) {}
189 [[nodiscard]]
auto valid() const noexcept ->
bool {
190 return pixels !=
nullptr && width > 0 && height > 0 && stride >= width;
194 auto put_pixel_unsafe(
const int pos_x,
const int pos_y,
195 const std::uint32_t argb)
const noexcept ->
void {
196 pixels[pos_y * stride + pos_x] = argb;
200 auto put_pixel(
const int pos_x,
const int pos_y,
201 const std::uint32_t argb)
const noexcept ->
void {
202 if (pos_x < 0 || pos_y < 0 || pos_x >= width || pos_y >= height) {
205 put_pixel_unsafe(pos_x, pos_y, argb);
209 if (pixels !=
nullptr && fb_desc >= 0) {
212 const size_t screensize =
213 static_cast<size_t>(stride) *
static_cast<size_t>(height) * 4;
214 if (screensize > 0) {
215 munmap(pixels, screensize);
223enum class TouchEventType : std::uint8_t {
231 TouchEventType type{};
234 std::uint64_t time_ms{0};
242 virtual ~ITouchInput() =
default;
246 virtual auto poll(TouchEvent& event) ->
bool = 0;
247 virtual auto get_path() -> std::string = 0;
248 virtual auto set_path(
const std::string& new_path) ->
bool = 0;
249 virtual auto start(std::string path =
"") ->
bool = 0;
250 virtual auto stop() ->
bool = 0;
254class EvdevTouchInput final :
public ITouchInput {
256 auto get_path() -> std::string
override {
return path_; }
257 auto start(
const std::string PATH =
"") ->
bool override {
262 std::scoped_lock lock(poll_mtx_);
263 fd_ = open(path_.c_str(), O_RDONLY | O_NONBLOCK);
265 perror(
"open evdev");
268 ioctl(fd_, EVIOCGRAB, 1);
271 auto stop() ->
bool override {
272 std::scoped_lock lock(poll_mtx_);
274 ioctl(fd_, EVIOCGRAB, 0);
281 auto set_path(
const std::string& new_path) ->
bool override {
287 explicit EvdevTouchInput(
const std::string& path) : fd_(-1) {
288 EvdevTouchInput::set_path(path);
292 explicit EvdevTouchInput(
const char* path)
293 : EvdevTouchInput(std::string(path)) {}
295 ~EvdevTouchInput()
override { stop(); }
297 EvdevTouchInput(
const EvdevTouchInput&) =
delete;
298 auto operator=(
const EvdevTouchInput&) -> EvdevTouchInput& =
delete;
300 EvdevTouchInput(EvdevTouchInput&& other) noexcept
302 cur_x_(other.cur_x_),
303 cur_y_(other.cur_y_),
304 touching_(other.touching_) {
306 other.cur_x_ = other.cur_y_ = 0;
307 other.touching_ =
false;
310 auto operator=(EvdevTouchInput&& other)
noexcept -> EvdevTouchInput& {
311 if (
this != &other) {
314 cur_x_ = other.cur_x_;
315 cur_y_ = other.cur_y_;
316 touching_ = other.touching_;
319 other.cur_x_ = other.cur_y_ = 0;
320 other.touching_ =
false;
325 auto poll(TouchEvent& event) ->
bool override {
326 std::scoped_lock lock(poll_mtx_);
333 bool coord_changed =
false;
334 bool has_type =
false;
335 auto next_type = TouchEventType::MOVE;
339 ssize_t n = read(fd_, &raw,
sizeof(raw));
341 if (errno == EAGAIN || errno == EWOULDBLOCK) {
348 if (n !=
static_cast<ssize_t
>(
sizeof(raw))) {
352 if (raw.type == EV_ABS) {
353 if (raw.code == ABS_X || raw.code == ABS_MT_POSITION_X) {
355 coord_changed =
true;
356 }
else if (raw.code == ABS_Y || raw.code == ABS_MT_POSITION_Y) {
358 coord_changed =
true;
365 if (raw.type == EV_KEY && raw.code == BTN_TOUCH) {
367 touching_ = raw.value != 0;
369 next_type = touching_ ? TouchEventType::DOWN : TouchEventType::UP;
374 if (raw.type == EV_SYN && raw.code == SYN_REPORT) {
376 if (has_type || (coord_changed && touching_)) {
377 event.type = has_type ? next_type : TouchEventType::MOVE;
378 event.position = {cur_x_, cur_y_};
381 static_cast<std::uint64_t
>(raw.time.tv_sec) * 1000ULL +
382 static_cast<std::uint64_t
>(raw.time.tv_usec) / 1000ULL;
387 coord_changed =
false;
396 std::mutex poll_mtx_{};
401 bool touching_{
false};
406 nk_user_font* normal{
nullptr};
407 nk_user_font* big{
nullptr};
408 nk_user_font* small{
nullptr};
412struct BackgroundView {
413 const std::uint32_t* pixels{
nullptr};
418 [[nodiscard]]
auto valid() const noexcept ->
bool {
419 return pixels !=
nullptr && width > 0 && height > 0 && stride >= width;
426 BackgroundView background;
433 virtual ~INkWindow() =
default;
439 virtual auto build_ui(nk_context& ctx,
const GuiResources& resources)
442 virtual auto has_new_data() ->
bool = 0;
452 TinyGui(FramebufferView& fb, ITouchInput& input)
noexcept;
456 auto add_window(std::shared_ptr<INkWindow> window) -> void;
459 auto clear_windows() -> void;
463 auto is_running() const noexcept ->
bool {
return running_; }
467 auto run(std::uint32_t frame_time_ms = K_DEFAULT_FRAME_TIME_MS) -> void;
470 auto stop() noexcept ->
void;
474 auto set_background_color(const std::uint32_t argb) noexcept ->
void {
475 background_color_ = argb;
479 auto set_use_vsync(
const bool value)
noexcept ->
void { use_vsync_ = value; }
484 auto set_use_background_clear(
const bool value)
noexcept ->
void {
485 use_background_clear_ = value;
490 auto init_nuklear() -> void;
491 auto shutdown_nuklear() -> void;
494 auto pump_input() -> void;
497 auto render_commands() -> void;
500 auto clear_background_to(std::uint32_t* dst,
int width,
int height,
501 int stride)
const -> void;
504 auto ensure_backbuffer() -> void;
507 [[nodiscard]]
auto background_view() const -> BackgroundView;
510 FramebufferView& fb_;
512 std::mutex start_mutex_;
513 std::vector<std::shared_ptr<INkWindow>> windows_;
515 nk_context* ctx_{
nullptr};
518 std::vector<std::uint8_t> atlas_alpha_;
521 nk_user_font* font_normal_{
nullptr};
522 nk_user_font* font_big_{
nullptr};
523 nk_user_font* font_small_{
nullptr};
526 std::vector<std::uint32_t>
528 std::vector<std::uint32_t>
530 bool have_saved_background_{
false};
532 std::atomic_bool running_{
false};
533 std::uint32_t background_color_{0xFF202020};
534 bool use_vsync_{
true};
535 bool use_background_clear_{
540 bool touch_down_{
false};
541 std::atomic_bool touch_event_{
false};
546 float cal_scale_x_{1.0f};
547 float cal_scale_y_{1.0f};
548 bool cal_valid_{
false};