Skip to content

Commit 8cb9027

Browse files
committed
feat: ✨ add touch listeners and wip render mode
1 parent f9c5299 commit 8cb9027

File tree

2 files changed

+172
-19
lines changed

2 files changed

+172
-19
lines changed

include/pros/devices/screen.hpp

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
#pragma once
22

3+
#include "pros/rtos.hpp"
34
#include "stdint.h"
45

56
#include <functional>
7+
#include <optional>
68

79
namespace zest {
810

11+
/**
12+
* @brief A friendlier, documented and safer API wrapper around the Vex SDK's display functions.
13+
*
14+
* @note Heavily inspired by vexide's display module: https://docs.rs/vexide/0.7.0/vexide/devices/display/index.html
15+
*/
916
struct Screen {
1017
/** Vertical height taken up by the user program in pixels. */
1118
static constexpr int16_t HEADER_HEIGHT = 32;
@@ -20,14 +27,14 @@ struct Screen {
2027
struct TouchEvent {
2128
enum class State {
2229
/** The screen has been released. */
23-
Release,
30+
Released,
2431
/** The screen has been touched. */
25-
Press,
32+
Pressed,
2633
/** The screen has been touched and is still being held. */
2734
Held
2835
};
2936

30-
/** The y coordinate of the touch. */
37+
/** The current state of the touch. */
3138
State state;
3239
/** The x coordinate of the touch. */
3340
int16_t x;
@@ -51,40 +58,87 @@ struct Screen {
5158
static TouchEvent get_last_touch();
5259

5360
/**
54-
* @brief Subscribes the listener to be called when the screen begins to be pressed.
61+
* @brief Subscribes the listener to be called when the screen touch state changes.
5562
*
5663
* Spawn a new task to check for events if it is not already running.
5764
* All listeners are called from this task.
5865
*
66+
* TODO: Elaborate on below?
5967
* @warning If you have multiple listeners, avoid using delays, which can delay other callbacks
6068
* from being called, and result in lost information.
6169
* For this reason, it is recommended library developers use get_last_touch() instead of this
6270
* function.
6371
*
64-
* @param listener_cb The function to call when the screen is pressed.
72+
* @param listener_cb The function to call when the screen's touch state changes.
73+
* @param state_filter Optional filter for the touch state. If provided, only events matching
74+
* this state will trigger the callback. For example, if state_filter ==
75+
* TouchEvent::State::Pressed, then the callback will only be called when the screen starts
76+
* being pressed.
77+
*/
78+
static void on_touched(
79+
std::function<void(const TouchEvent&)> listener_cb,
80+
std::optional<TouchEvent::State> state_filter = std::nullopt
81+
);
82+
83+
/**
84+
* @brief Subscribes the listener to be called when the screen begins to be pressed.
85+
* Wrapper for Screen::on_touched() with state_filter set to TouchEvent::State::Pressed.
86+
* @see Screen::on_touched()
6587
*/
6688
static void on_pressed(std::function<void(const TouchEvent&)> listener_cb);
6789

6890
/**
6991
* @brief Subscribes the listener to be called when the screen begins to be released.
70-
*
71-
* Spawn a new task to check for events if it is not already running.
72-
* All listeners are called from this task.
73-
*
74-
* @warning If you have multiple listeners, avoid using delays, which can delay other callbacks
75-
* from being called, and result in lost information.
76-
* For this reason, it is recommended library developers use get_last_touch() instead of this
77-
* function.
78-
*
79-
* @param listener_cb The function to call when the screen is pressed.
92+
* Wrapper for Screen::on_touched() with state_filter set to TouchEvent::State::Released.
93+
* @see Screen::on_touched()
8094
*/
8195
static void on_released(std::function<void(const TouchEvent&)> listener_cb);
8296

83-
// TODO: Should there be a on_held() function?
97+
/**
98+
* @brief Subscribes the listener to be called when the screen begins to be held.
99+
* Wrapper for Screen::on_touched() with state_filter set to TouchEvent::State::Held.
100+
* @see Screen::on_touched()
101+
*/
102+
static void on_held(std::function<void(const TouchEvent&)> listener_cb);
103+
104+
/** @brief Determines where screen operations should be written. Immediate is the default. */
105+
enum class RenderMode {
106+
/**
107+
* Draw operations are immediately applied to the screen without the need to call
108+
* Screen::render().
109+
* This is the default mode.
110+
*/
111+
Immediate,
112+
/**
113+
* Draw calls are written to an intermediate display buffer, rather than directly drawn to
114+
* the screen. This buffer can later be applied using Screen::render().
115+
*
116+
* This mode is necessary for preventing screen tearing when drawing at high speeds.
117+
*/
118+
DoubleBuffered,
119+
};
120+
121+
/**
122+
* @brief Changes the render mode of the screen.
123+
* @param new_mode The new render mode to set.
124+
* @see Screen::RenderMode
125+
*/
126+
static void set_render_mode(RenderMode new_mode);
127+
128+
/**
129+
* @brief Gets the current render mode of the screen.
130+
* @return The current render mode of the screen.
131+
* @see Screen::RenderMode
132+
*/
133+
static RenderMode get_render_mode();
84134

85-
static void set_render_mode();
86-
static void get_render_mode();
135+
/**
136+
* @brief Flushes the screen's double buffer it is enabled.
137+
* This does nothing in the immediate render mode, but is required in the immediate render mode.
138+
* @see Screen::RenderMode
139+
*/
87140
static void render();
141+
88142
static void scroll();
89143
static void scroll_region();
90144

@@ -105,6 +159,10 @@ struct Screen {
105159
static void clear(auto color /* = TODO*/);
106160

107161
static void draw_buffer();
162+
163+
private:
164+
static RenderMode m_render_mode;
165+
static pros::Mutex m_mutex;
108166
};
109167

110168
} // namespace zest

src/devices/screen.cpp

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,99 @@
11
#include "pros/devices/screen.hpp"
22

3+
#include "pros/rtos.hpp"
34
#include "v5_api_patched.h"
4-
#include "v5_apitypes_patched.h"
5+
#include "v5_apitypes_patched.h"
6+
7+
#include <utility>
8+
#include <vector>
9+
10+
#ifndef ZESTCODE_CONFIG_SCREEN_TOUCH_LISTENER_TASK_INTERVAL
11+
// Default to 16 ms if not defined
12+
#define ZESTCODE_CONFIG_SCREEN_TOUCH_LISTENER_TASK_INTERVAL 1000 / zest::Screen::FRAMERATE
13+
#endif
14+
15+
#ifndef ZESTCODE_CONFIG_SCREEN_TOUCH_LISTENER_WARNING_THRESHOLD
16+
// Default to 5 ms if not defined
17+
#define ZESTCODE_CONFIG_SCREEN_TOUCH_LISTENER_WARNING_THRESHOLD 5
18+
#endif
19+
namespace zest {
20+
21+
void Screen::on_touched(
22+
std::function<void(const TouchEvent&)> listener_cb,
23+
std::optional<Screen::TouchEvent::State> state_filter
24+
) {
25+
struct Listener {
26+
std::function<void(const TouchEvent&)> callback;
27+
std::optional<Screen::TouchEvent::State> filter;
28+
};
29+
30+
static std::vector<Listener> listeners;
31+
listeners.emplace_back(std::move(listener_cb), state_filter);
32+
33+
// Wait! There's a sdk function that does this for us!: vexTouchUserCallbackSet()
34+
// However, using this would cause the callback to be called from the same task that calls
35+
// vexTasksRun(), the system_daemon task. This is potentially quite dangerous, as the user could
36+
// wait on device data which will never come since the system_daemon task would never reach
37+
// vexTasksRun() again to update the data.
38+
// Instead we use a separate task to check for touch events and call the listeners.
39+
40+
// TODO: Determine thread safety? (What happens if task A calls this, and while touch_task is
41+
// being constructed, task B preempts it and calls this again?)
42+
static pros::Task touch_task([] {
43+
while (true) {
44+
const auto touch = Screen::get_last_touch();
45+
const auto state = touch.state;
46+
static Screen::TouchEvent::State prev_state = state;
47+
static size_t prev_time = pros::millis();
48+
49+
if (prev_state != state)
50+
for (const auto& listener : listeners)
51+
if (!listener.filter.has_value() || listener.filter.value() == state) {
52+
const size_t start_time = pros::millis();
53+
listener.callback(touch);
54+
55+
// TODO: Determine the time taken by the actual callback. For example, if
56+
// the callback was preempted mid-way through, that time spend being preempt
57+
// should not count.
58+
const size_t elapsed_time = pros::millis() - start_time;
59+
if (listeners.size() > 1
60+
&& elapsed_time
61+
> ZESTCODE_CONFIG_SCREEN_TOUCH_LISTENER_WARNING_THRESHOLD) {
62+
// TODO: Log a warning if the callback took too long
63+
}
64+
}
65+
prev_state = state;
66+
67+
// TODO: I recall there being problems with using plain pros::c::task_delay_until with
68+
// lemlib. Do those apply here?
69+
70+
// Use pros::c::task_delay_until() instead of pros::delay() to avoid drift
71+
pros::c::task_delay_until(
72+
&prev_time,
73+
ZESTCODE_CONFIG_SCREEN_TOUCH_LISTENER_TASK_INTERVAL
74+
);
75+
}
76+
});
77+
}
78+
79+
void Screen::on_pressed(std::function<void(const TouchEvent&)> listener_cb) {
80+
on_touched(std::move(listener_cb), TouchEvent::State::Pressed);
81+
}
82+
83+
void Screen::on_released(std::function<void(const TouchEvent&)> listener_cb) {
84+
on_touched(std::move(listener_cb), TouchEvent::State::Released);
85+
}
86+
87+
void Screen::on_held(std::function<void(const TouchEvent&)> listener_cb) {
88+
on_touched(std::move(listener_cb), TouchEvent::State::Held);
89+
}
90+
91+
void Screen::set_render_mode(Screen::RenderMode new_mode) {
92+
// TODO: Implement
93+
}
94+
95+
Screen::RenderMode Screen::get_render_mode() {
96+
// TODO: Implement
97+
}
98+
99+
} // namespace zest

0 commit comments

Comments
 (0)