Expressive Eyes APIs
The ExpressiveEyes class provides an animated expressive eyes system for displays using simple blob shapes. It supports multiple expressions, smooth eye movement, blinking, and optional physics-based pupil movement.
The component uses a callback-based drawing system, allowing you to implement custom renderers for different display types and visual styles. Example drawer implementations are provided showing both realistic eyes with pupils and minimalist monochrome designs.
Features include:
Multiple expressions (happy, sad, angry, surprised, neutral, sleepy, bored, wink_left, wink_right)
Smooth eye movement with look_at positioning
Automatic blinking with configurable intervals
Optional pupils with physics-based movement
Eyebrows and cheeks for enhanced expressions
Smooth expression transitions with blending
Customizable colors and sizes
Frame-based animation system
The drawing callback receives complete eye state information including position, size, expression parameters, pupil position, eyebrow configuration, and more, giving you full control over the rendering.
API Reference
Header File
Classes
-
class ExpressiveEyes : public espp::BaseComponent
Expressive Eyes Animation Component.
Renders animated expressive eyes using simple blob shapes. Eyes can blink, look around, change expression, and display various emotions.
Features:
Smooth eye movement and blinking
Multiple expressions (happy, sad, angry, surprised, etc.)
Optional pupils with physics-based movement
Eyebrows and cheeks for enhanced expressions
Customizable colors and sizes
Frame-based animation system
Expressive Eyes Example
// Initialize the board Board &board = Board::get(); board.set_log_level(espp::Logger::Verbosity::INFO); // Initialize the LCD if (!board.initialize_lcd()) { logger.error("Failed to initialize LCD!"); return; } // Set up pixel buffer (50 lines) static constexpr size_t pixel_buffer_size = board.lcd_width() * 50; // Initialize the display if (!board.initialize_display(pixel_buffer_size)) { logger.error("Failed to initialize display!"); return; } // Get screen dimensions int screen_width = board.lcd_width(); int screen_height = board.lcd_height(); logger.info("Display size: {}x{}", screen_width, screen_height); // Disable scrollbars on screen lv_obj_clear_flag(lv_screen_active(), LV_OBJ_FLAG_SCROLLABLE); // Create main canvas for drawing everything lv_obj_t *canvas = lv_canvas_create(lv_screen_active()); // Allocate buffer for main canvas (RGB565 = 2 bytes per pixel) static lv_color_t *canvas_buffer = nullptr; canvas_buffer = (lv_color_t *)heap_caps_malloc(screen_width * screen_height * sizeof(lv_color_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (!canvas_buffer) { logger.error("Failed to allocate canvas buffer!"); return; } lv_canvas_set_buffer(canvas, canvas_buffer, screen_width, screen_height, LV_COLOR_FORMAT_RGB565); lv_obj_center(canvas); // Create the drawer based on menuconfig selection std::unique_ptr<eye_drawer::EyeDrawer> drawer; #if CONFIG_EXPRESSIVE_EYES_FULL_FEATURED logger.info("Using Full Featured drawer"); drawer = std::make_unique<eye_drawer::FullFeaturedDrawer>( eye_drawer::FullFeaturedDrawer::Config{.screen_width = screen_width, .screen_height = screen_height, .canvas = canvas, .canvas_buffer = canvas_buffer, .lvgl_mutex = lvgl_mutex}); #elif CONFIG_EXPRESSIVE_EYES_MONOCHROME_BLUE logger.info("Using Monochrome Blue drawer"); drawer = std::make_unique<eye_drawer::MonochromeBlueDrawer>( eye_drawer::MonochromeBlueDrawer::Config{.screen_width = screen_width, .screen_height = screen_height, .canvas = canvas, .canvas_buffer = canvas_buffer, .lvgl_mutex = lvgl_mutex}); #else #error "No drawing method selected in menuconfig!" #endif // Get the draw callback from the drawer auto draw_eyes = drawer->get_draw_callback(); // Configure expressive eyes with adaptive sizing - make eyes larger int large_eye_width = screen_width * 0.35f; // 35% of screen width each int large_eye_height = screen_height * 0.55f; // 55% of screen height int large_spacing = screen_width * 0.55f; // Space between eye centers espp::ExpressiveEyes::Config config{.screen_width = screen_width, .screen_height = screen_height, .eye_spacing = large_spacing, .eye_width = large_eye_width, .eye_height = large_eye_height, .blink_duration = 0.12f, .blink_interval = 4.0f, .enable_auto_blink = true, .enable_pupil_physics = true, .on_draw = draw_eyes, .log_level = espp::Logger::Verbosity::WARN}; espp::ExpressiveEyes eyes(config); // start a simple thread to do the lv_task_handler every 16ms espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool { { std::lock_guard<std::recursive_mutex> lock(lvgl_mutex); lv_task_handler(); } std::unique_lock<std::mutex> lock(m); cv.wait_for(lock, 16ms); return false; }, .task_config = { .name = "lv_task", .stack_size_bytes = 16 * 1024, }}); lv_task.start(); logger.info("Expressive eyes initialized"); // Test different expressions using array iteration logger.info("Testing different expressions..."); // Cycle through each expression preset to demonstrate different emotional states const espp::ExpressiveEyes::Expression expressions[] = { espp::ExpressiveEyes::Expression::NEUTRAL, espp::ExpressiveEyes::Expression::HAPPY, espp::ExpressiveEyes::Expression::SAD, espp::ExpressiveEyes::Expression::ANGRY, espp::ExpressiveEyes::Expression::SURPRISED, espp::ExpressiveEyes::Expression::SLEEPY, espp::ExpressiveEyes::Expression::BORED, espp::ExpressiveEyes::Expression::WINK_LEFT, espp::ExpressiveEyes::Expression::WINK_RIGHT}; for (const auto &expr : expressions) { logger.info("Expression: {}", expr); eyes.set_expression(expr); auto start = std::chrono::steady_clock::now(); while (std::chrono::duration<float>(std::chrono::steady_clock::now() - start).count() < 3.0f) { eyes.update(0.016f); // 16ms update at ~60fps std::this_thread::sleep_for(16ms); } } // Test look_at functionality using array iteration logger.info("Testing look_at functionality"); eyes.set_expression(espp::ExpressiveEyes::Expression::NEUTRAL); // Demonstrate directional looking in the 4 cardinal directions plus center struct LookDirection { const char *name; float x; float y; }; const LookDirection look_directions[] = {{"left", -1.0f, 0.0f}, {"right", 1.0f, 0.0f}, {"up", 0.0f, -1.0f}, {"down", 0.0f, 1.0f}, {"center", 0.0f, 0.0f}}; for (const auto &dir : look_directions) { logger.info("Looking {}", dir.name); eyes.look_at(dir.x, dir.y); auto start = std::chrono::steady_clock::now(); while (std::chrono::duration<float>(std::chrono::steady_clock::now() - start).count() < 1.5f) { eyes.update(0.016f); // 16ms update at ~60fps std::this_thread::sleep_for(16ms); } } // Random demo mode - continuously looks around and changes expressions logger.info("Starting random demo mode - will run continuously"); // Seed random number generator with current time srand(time(nullptr)); // Reset to neutral expression and center gaze eyes.set_expression(espp::ExpressiveEyes::Expression::NEUTRAL); eyes.look_at(0.0f, 0.0f); // Random mode state - tracks when to trigger next action float time_until_next_look = 2.0f + (rand() % 4000) / 1000.0f; // 2-6 seconds float time_until_next_expression = 5.0f + (rand() % 10000) / 1000.0f; // 5-15 seconds float look_timer = 0.0f; float expression_timer = 0.0f; // Animation loop - runs indefinitely for continuous desk display auto last_time = std::chrono::steady_clock::now(); while (true) { auto now = std::chrono::steady_clock::now(); float dt = std::chrono::duration<float>(now - last_time).count(); last_time = now; // Update timers look_timer += dt; expression_timer += dt; // Randomly look around - gives eyes natural movement if (look_timer >= time_until_next_look) { float look_x = ((rand() % 2000) - 1000) / 1000.0f; // -1.0 to 1.0 float look_y = ((rand() % 2000) - 1000) / 1000.0f; // -1.0 to 1.0 eyes.look_at(look_x, look_y); look_timer = 0.0f; time_until_next_look = 2.0f + (rand() % 4000) / 1000.0f; // 2-6 seconds } // Randomly change expression (weighted toward neutral for natural behavior) if (expression_timer >= time_until_next_expression) { int expr_choice = rand() % 10; if (expr_choice < 5) { // 50% chance: stay neutral eyes.set_expression(espp::ExpressiveEyes::Expression::NEUTRAL); } else if (expr_choice < 7) { // 20% chance: happy eyes.set_expression(espp::ExpressiveEyes::Expression::HAPPY); } else if (expr_choice < 8) { // 10% chance: surprised eyes.set_expression(espp::ExpressiveEyes::Expression::SURPRISED); } else if (expr_choice < 9) { // 10% chance: sad eyes.set_expression(espp::ExpressiveEyes::Expression::SAD); } else { // 10% chance: angry eyes.set_expression(espp::ExpressiveEyes::Expression::ANGRY); } expression_timer = 0.0f; time_until_next_expression = 5.0f + (rand() % 10000) / 1000.0f; // 5-15 seconds } // Update and render eyes (calls draw callback) eyes.update(dt); std::this_thread::sleep_for(16ms); // ~60 FPS }
Public Types
-
enum class Expression
Eye expression presets.
Values:
-
enumerator NEUTRAL
Normal open eyes.
-
enumerator HAPPY
Squinted happy eyes with raised cheeks.
-
enumerator SAD
Droopy sad eyes with angled eyebrows.
-
enumerator ANGRY
Angled angry eyes with furrowed eyebrows.
-
enumerator SURPRISED
Wide open eyes with raised eyebrows.
-
enumerator SLEEPY
Droopy half-closed eyes with low eyebrows.
-
enumerator BORED
Half-closed eyes with neutral expression.
-
enumerator WINK_LEFT
Left eye closed.
-
enumerator WINK_RIGHT
Right eye closed.
-
enumerator NEUTRAL
Public Functions
-
explicit ExpressiveEyes(const Config &config)
Construct expressive eyes.
- Parameters:
config – Configuration structure
-
void update(float dt)
Update animation (call this every frame)
- Parameters:
dt – Delta time since last update in seconds
-
void look_at(float x, float y)
Set target look direction.
- Parameters:
x – Horizontal look direction (-1.0 to 1.0, 0=center)
y – Vertical look direction (-1.0 to 1.0, 0=center)
-
void set_expression(Expression expr)
Set expression.
- Parameters:
expr – Expression to display
-
void blink()
Trigger a blink.
-
inline Expression get_expression() const
Get current expression.
- Returns:
Current expression
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
Public Static Functions
-
static ExpressionState get_preset_expression(Expression expr)
Get preset expression state.
- Parameters:
expr – Expression preset
- Returns:
Expression state configuration
-
struct Config
Configuration for expressive eyes.
Public Members
-
int screen_width = {320}
Screen width in pixels.
-
int screen_height = {240}
Screen height in pixels.
-
int eye_spacing = {100}
Distance between eye centers.
-
int eye_width = {60}
Base eye width.
-
int eye_height = {80}
Base eye height.
-
float blink_duration = {0.12f}
Blink duration in seconds.
-
float blink_interval = {4.0f}
Average time between blinks.
-
bool enable_auto_blink = {true}
Automatic random blinking.
-
bool enable_pupil_physics = {true}
Smooth pupil movement.
-
draw_callback on_draw = {nullptr}
Drawing callback.
-
int screen_width = {320}
-
struct ExpressionState
Complete expression state.
Public Members
-
float eye_width_scale = {1.0f}
Eye width multiplier.
-
float eye_height_scale = {1.0f}
Eye height multiplier.
-
float eye_rotation = {0.0f}
Eye rotation in radians.
-
float top_curve = {0.5f}
Top eyelid curve (0=flat, 1=round)
-
float bottom_curve = {0.5f}
Bottom eyelid curve (0=flat, 1=round)
-
float eye_offset_y = {0.0f}
Vertical offset for eye position.
-
float cheek_offset_y = {0.0f}
Vertical offset for cheek position.
-
float eye_width_scale = {1.0f}
-
struct EyeState
Single eye render data.