Includes some misc cleanup of haptic double-triggering (or non-triggering), since those cases all end up being TTS event double-reporting, which to me crosses the threshold from "annoying" to "usability issue"custom
							parent
							
								
									ef812a53e5
								
							
						
					
					
						commit
						2ff8eac022
					
				| @ -0,0 +1,97 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright 2024 jacqueline <me@jacqueline.id.au> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: GPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "input/feedback_tts.hpp" | ||||||
|  | 
 | ||||||
|  | #include <cstdint> | ||||||
|  | #include <variant> | ||||||
|  | 
 | ||||||
|  | #include "lvgl/lvgl.h" | ||||||
|  | 
 | ||||||
|  | #include "core/lv_event.h" | ||||||
|  | #include "core/lv_group.h" | ||||||
|  | #include "core/lv_obj.h" | ||||||
|  | #include "core/lv_obj_class.h" | ||||||
|  | #include "core/lv_obj_tree.h" | ||||||
|  | #include "extra/widgets/list/lv_list.h" | ||||||
|  | #include "tts/events.hpp" | ||||||
|  | #include "widgets/lv_label.h" | ||||||
|  | 
 | ||||||
|  | #include "tts/events.hpp" | ||||||
|  | #include "tts/provider.hpp" | ||||||
|  | 
 | ||||||
|  | namespace input { | ||||||
|  | 
 | ||||||
|  | TextToSpeech::TextToSpeech(tts::Provider& tts) | ||||||
|  |     : tts_(tts), last_obj_(nullptr) {} | ||||||
|  | 
 | ||||||
|  | auto TextToSpeech::feedback(lv_group_t* group, uint8_t event_type) -> void { | ||||||
|  |   if (group != last_group_) { | ||||||
|  |     last_group_ = group; | ||||||
|  |     last_obj_ = nullptr; | ||||||
|  |     if (group) { | ||||||
|  |       tts_.feed(tts::SimpleEvent::kContextChanged); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (group) { | ||||||
|  |     lv_obj_t* focused = lv_group_get_focused(group); | ||||||
|  |     if (focused == last_obj_) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     last_obj_ = focused; | ||||||
|  |     if (focused != nullptr) { | ||||||
|  |       describe(*focused); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto TextToSpeech::describe(lv_obj_t& obj) -> void { | ||||||
|  |   if (lv_obj_check_type(&obj, &lv_btn_class) || | ||||||
|  |       lv_obj_check_type(&obj, &lv_list_btn_class)) { | ||||||
|  |     auto desc = findDescription(obj); | ||||||
|  |     tts_.feed(tts::SelectionChanged{ | ||||||
|  |         .new_selection = | ||||||
|  |             tts::SelectionChanged::Selection{ | ||||||
|  |                 .description = desc, | ||||||
|  |                 .is_interactive = true, | ||||||
|  |             }, | ||||||
|  |     }); | ||||||
|  |   } else { | ||||||
|  |     auto desc = findDescription(obj); | ||||||
|  |     tts_.feed(tts::SelectionChanged{ | ||||||
|  |         .new_selection = | ||||||
|  |             tts::SelectionChanged::Selection{ | ||||||
|  |                 .description = desc, | ||||||
|  |                 .is_interactive = false, | ||||||
|  |             }, | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | auto TextToSpeech::findDescription(lv_obj_t& obj) | ||||||
|  |     -> std::optional<std::string> { | ||||||
|  |   if (lv_obj_get_child_cnt(&obj) > 0) { | ||||||
|  |     for (size_t i = 0; i < lv_obj_get_child_cnt(&obj); i++) { | ||||||
|  |       auto res = findDescription(*lv_obj_get_child(&obj, i)); | ||||||
|  |       if (res) { | ||||||
|  |         return res; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (lv_obj_check_type(&obj, &lv_label_class)) { | ||||||
|  |     std::string text = lv_label_get_text(&obj); | ||||||
|  |     if (!text.empty()) { | ||||||
|  |       return text; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | }  // namespace input
 | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright 2024 jacqueline <me@jacqueline.id.au> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: GPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <cstdint> | ||||||
|  | 
 | ||||||
|  | #include "core/lv_obj.h" | ||||||
|  | #include "drivers/haptics.hpp" | ||||||
|  | 
 | ||||||
|  | #include "input/feedback_device.hpp" | ||||||
|  | #include "tts/events.hpp" | ||||||
|  | #include "tts/provider.hpp" | ||||||
|  | 
 | ||||||
|  | namespace input { | ||||||
|  | 
 | ||||||
|  | class TextToSpeech : public IFeedbackDevice { | ||||||
|  |  public: | ||||||
|  |   TextToSpeech(tts::Provider&); | ||||||
|  | 
 | ||||||
|  |   auto feedback(lv_group_t*, uint8_t event_type) -> void override; | ||||||
|  | 
 | ||||||
|  |  private: | ||||||
|  |   tts::Provider& tts_; | ||||||
|  | 
 | ||||||
|  |   auto describe(lv_obj_t&) -> void; | ||||||
|  |   auto findDescription(lv_obj_t&) -> std::optional<std::string>; | ||||||
|  | 
 | ||||||
|  |   lv_group_t* last_group_; | ||||||
|  |   lv_obj_t* last_obj_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace input
 | ||||||
| @ -0,0 +1,41 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright 2024 jacqueline <me@jacqueline.id.au> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: GPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <variant> | ||||||
|  | 
 | ||||||
|  | namespace tts { | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * 'Simple' TTS events are events that do not have any extra event-specific | ||||||
|  |  * details. | ||||||
|  |  */ | ||||||
|  | enum class SimpleEvent { | ||||||
|  |   /*
 | ||||||
|  |    * Indicates that the screen's content has substantially changed. e.g. a new | ||||||
|  |    * screen has been pushed. | ||||||
|  |    */ | ||||||
|  |   kContextChanged, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Event indicating that the currently selected LVGL object has changed. | ||||||
|  |  */ | ||||||
|  | struct SelectionChanged { | ||||||
|  |   struct Selection { | ||||||
|  |     std::optional<std::string> description; | ||||||
|  |     bool is_interactive; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   std::optional<Selection> new_selection; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | using Event = std::variant<SimpleEvent, SelectionChanged>; | ||||||
|  | 
 | ||||||
|  | }  // namespace tts
 | ||||||
| @ -0,0 +1,38 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright 2024 jacqueline <me@jacqueline.id.au> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: GPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include "tts/provider.hpp" | ||||||
|  | 
 | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <variant> | ||||||
|  | 
 | ||||||
|  | #include "esp_log.h" | ||||||
|  | 
 | ||||||
|  | #include "tts/events.hpp" | ||||||
|  | 
 | ||||||
|  | namespace tts { | ||||||
|  | 
 | ||||||
|  | [[maybe_unused]] static constexpr char kTag[] = "tts"; | ||||||
|  | 
 | ||||||
|  | Provider::Provider() {} | ||||||
|  | 
 | ||||||
|  | auto Provider::feed(const Event& e) -> void { | ||||||
|  |   if (std::holds_alternative<SimpleEvent>(e)) { | ||||||
|  |     // ESP_LOGI(kTag, "context changed");
 | ||||||
|  |   } else if (std::holds_alternative<SelectionChanged>(e)) { | ||||||
|  |     auto ev = std::get<SelectionChanged>(e); | ||||||
|  |     if (!ev.new_selection) { | ||||||
|  |       // ESP_LOGI(kTag, "no selection");
 | ||||||
|  |     } else { | ||||||
|  |       // ESP_LOGI(kTag, "new selection: '%s', interactive? %i",
 | ||||||
|  |       // ev.new_selection->description.value_or("").c_str(),
 | ||||||
|  |       // ev.new_selection->is_interactive);
 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | }  // namespace tts
 | ||||||
| @ -0,0 +1,23 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright 2024 jacqueline <me@jacqueline.id.au> | ||||||
|  |  * | ||||||
|  |  * SPDX-License-Identifier: GPL-3.0-only | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <variant> | ||||||
|  | 
 | ||||||
|  | #include "tts/events.hpp" | ||||||
|  | 
 | ||||||
|  | namespace tts { | ||||||
|  | 
 | ||||||
|  | class Provider { | ||||||
|  |  public: | ||||||
|  |   Provider(); | ||||||
|  |   auto feed(const Event&) -> void; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace tts
 | ||||||
					Loading…
					
					
				
		Reference in new issue