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