15#include <imgui_stdlib.h>
24using namespace std::literals;
33 popupAction[INFO] = NO_POPUP;
35 popupAction[LOGLEVEL_ERROR] = MODAL_POPUP;
36 popupAction[PROGRESS] = NO_POPUP;
37 openLogAction[INFO] = NO_OPEN_LOG;
38 openLogAction[
WARNING] = NO_OPEN_LOG;
39 openLogAction[LOGLEVEL_ERROR] = NO_OPEN_LOG;
40 openLogAction[PROGRESS] = NO_OPEN_LOG;
41 osdAction[INFO] = SHOW_OSD;
43 osdAction[LOGLEVEL_ERROR] = NO_OSD;
44 osdAction[PROGRESS] = NO_OSD;
48 listenerHandle = cliComm.addListener(std::make_unique<Listener>(*
this));
61 buf.appendf(
"popupActions=[%d %d %d]\n",
62 popupAction[LOGLEVEL_ERROR],
65 buf.appendf(
"openLogActions=[%d %d %d]\n",
66 openLogAction[LOGLEVEL_ERROR],
69 buf.appendf(
"osdActions=[%d %d %d]\n",
70 osdAction[LOGLEVEL_ERROR],
73 buf.appendf(
"fadeOutDuration=[%f %f %f]\n",
74 double(colorSequence[LOGLEVEL_ERROR][2].start),
75 double(colorSequence[
WARNING][2].start),
76 double(colorSequence[INFO][2].start));
83 }
else if (name ==
"popupActions"sv) {
84 std::array<int, 3> a = {};
85 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
86 popupAction[LOGLEVEL_ERROR] = PopupAction(a[0]);
87 popupAction[
WARNING] = PopupAction(a[1]);
88 popupAction[INFO] = PopupAction(a[2]);
90 }
else if (name ==
"openLogActions"sv) {
91 std::array<int, 3> a = {};
92 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
93 openLogAction[LOGLEVEL_ERROR] = OpenLogAction(a[0]);
94 openLogAction[
WARNING] = OpenLogAction(a[1]);
95 openLogAction[INFO] = OpenLogAction(a[2]);
97 }
else if (name ==
"osdActions"sv) {
98 std::array<int, 3> a = {};
99 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
100 osdAction[LOGLEVEL_ERROR] = OsdAction(a[0]);
101 osdAction[
WARNING] = OsdAction(a[1]);
102 osdAction[INFO] = OsdAction(a[2]);
104 }
else if (name ==
"fadeOutDuration"sv) {
105 std::array<float, 3> a = {};
106 if (sscanf(value.
c_str(),
"[%f %f %f]", &a[0], &a[1], &a[2]) == 3) {
107 colorSequence[LOGLEVEL_ERROR][2].start = a[0];
108 colorSequence[
WARNING][2].start = a[1];
109 colorSequence[INFO][2].start = a[2];
124template<std::predicate<std::
string_view, std::
string_view> Filter = always_true>
128 for (
const auto& message : messages) {
129 auto [color, prefix_] = [&]() -> std::pair<ImU32, std::string_view> {
130 switch (message.level) {
137 auto prefix = prefix_;
138 if (std::invoke(filter, prefix, message.text)) {
149bool ImGuiMessages::paintButtons()
151 ImGui::SetCursorPosX(40.0f);
152 bool close = ImGui::Button(
"Ok");
153 ImGui::SameLine(0.0f, 30.0f);
154 if (ImGui::SmallButton(
"Configure...")) {
161void ImGuiMessages::paintModal()
165 ImGui::OpenPopup(
"Message");
169 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, {0.5f, 0.5f});
170 im::PopupModal(
"Message", &open, ImGuiWindowFlags_AlwaysAutoResize, [&]{
171 printMessages(modalMessages);
172 bool close = paintButtons();
173 if (!open || close) {
174 modalMessages.clear();
175 ImGui::CloseCurrentPopup();
180void ImGuiMessages::paintPopup()
185 if (!ImGui::IsPopupOpen(
"popup-message")) {
186 while (popupMessages.size() > doOpenPopup) {
187 popupMessages.pop_back();
191 ImGui::OpenPopup(
"popup-message");
195 printMessages(popupMessages);
196 bool close = paintButtons();
197 if (close) ImGui::CloseCurrentPopup();
201void ImGuiMessages::paintProgress()
203 if (doOpenProgress) {
204 doOpenProgress =
false;
205 ImGui::OpenPopup(
"popup-progress");
209 if (progressFraction >= 1.0f) {
210 ImGui::CloseCurrentPopup();
213 if (progressFraction >= 0.0f) {
214 ImGui::ProgressBar(progressFraction);
217 progressTime = fmodf(progressTime + ImGui::GetIO().DeltaTime, 2.0f);
218 float fraction = (progressTime < 1.0f) ? progressTime : (2.0f - progressTime);
219 ImGui::ProgressBar(fraction, {},
"");
225void ImGuiMessages::paintOSD()
227 auto getColors = [&](
const ColorSequence& seq,
float t) -> std::optional<Colors> {
229 const auto& step0 = seq[i + 0];
230 const auto& step1 = seq[i + 1];
231 if (
t < step1.start) {
233 auto x = int(256.0f * (
t / step1.start));
234 auto tCol = p.lerp(step0.colors.text, step1.colors.text, x);
235 auto bCol = p.lerp(step0.colors.background, step1.colors.background, x);
236 return Colors{tCol, bCol};
243 const auto& style = ImGui::GetStyle();
244 gl::vec2 offset = style.FramePadding;
245 const auto* mainViewPort = ImGui::GetMainViewport();
249 DrawInfo(std::string m,
gl::vec2 s,
float y, uint32_t
t, uint32_t b)
250 : message(
std::move(m)), boxSize(s), yPos(y), textCol(
t), bgCol(b) {}
255 uint32_t textCol, bgCol;
257 std::vector<DrawInfo> drawInfo;
261 float delta = ImGui::GetIO().DeltaTime;
262 std::erase_if(osdMessages, [&](OsdMessage& message) {
263 message.time += delta;
264 auto colors = getColors(colorSequence[message.level], message.time);
265 if (!colors)
return true;
267 auto& text = message.text;
269 gl::vec2 boxSize = textSize + 2.0f * offset;
270 drawInfo.emplace_back(text, boxSize, y, colors->text, colors->background);
271 y += boxSize.y + style.ItemSpacing.y;
272 width = std::max(width, boxSize.x);
275 if (drawInfo.empty())
return;
277 int flags = ImGuiWindowFlags_NoMove
278 | ImGuiWindowFlags_NoBackground
279 | ImGuiWindowFlags_NoSavedSettings
280 | ImGuiWindowFlags_NoDocking
281 | ImGuiWindowFlags_NoNav
282 | ImGuiWindowFlags_NoDecoration
283 | ImGuiWindowFlags_NoInputs
284 | ImGuiWindowFlags_NoFocusOnAppearing;
285 ImGui::SetNextWindowViewport(mainViewPort->ID);
286 ImGui::SetNextWindowPos(
gl::vec2(mainViewPort->WorkPos) +
gl::vec2(style.ItemSpacing));
287 ImGui::SetNextWindowSize({width, y});
288 im::Window(
"OSD messages",
nullptr, flags, [&]{
289 auto* drawList = ImGui::GetWindowDrawList();
290 gl::vec2 windowPos = ImGui::GetWindowPos();
291 for (
const auto& [message, boxSize, yPos, textCol, bgCol] : drawInfo) {
293 drawList->AddRectFilled(pos, pos + boxSize, bgCol);
294 drawList->AddText(pos + offset, textCol, message.data(), message.data() + message.size());
299void ImGuiMessages::paintLog()
303 ImGui::SetNextWindowFocus();
305 ImGui::SetNextWindowSize(
gl::vec2{40, 14} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
307 const auto& style = ImGui::GetStyle();
308 auto buttonHeight = ImGui::GetFontSize() + 2.0f * style.FramePadding.y + style.ItemSpacing.y;
309 im::Child(
"messages", {0.0f, -buttonHeight}, ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar, [&]{
310 printMessages(allMessages, [&](std::string_view prefix, std::string_view message) {
311 if (filterLog.empty())
return true;
313 return ranges::all_of(StringOp::split_view<StringOp::EmptyParts::REMOVE>(filterLog,
' '),
317 if (ImGui::Button(
"Clear")) {
321 ImGui::SameLine(0.0f, 30.0f);
325 ImGui::SetNextItemWidth(-size);
326 ImGui::InputTextWithHint(
"##filter",
"enter filter terms", &filterLog);
327 ImGui::SameLine(0.0f, 30.0f);
328 if (ImGui::SmallButton(
"Configure...")) {
334void ImGuiMessages::paintConfigure()
336 ImGui::SetNextWindowSize(
gl::vec2{24, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
341 im::Table(
"table", 4, ImGuiTableFlags_SizingFixedFit, [&]{
342 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_None, 0);
343 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
344 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
345 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
347 if (ImGui::TableNextColumn()) { }
352 if (ImGui::TableNextColumn()) {
355 ImGui::TableNextRow();
356 if (ImGui::TableNextColumn()) {
359 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
360 if (ImGui::TableNextColumn()) {
361 ImGui::RadioButton(
tmpStrCat(
"##modal", std::to_underlying(level)).c_str(), &popupAction[level], MODAL_POPUP);
364 if (ImGui::TableNextColumn()) {
367 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
368 if (ImGui::TableNextColumn()) {
369 ImGui::RadioButton(
tmpStrCat(
"##popup", std::to_underlying(level)).c_str(), &popupAction[level], POPUP);
372 if (ImGui::TableNextColumn()) {
375 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
376 if (ImGui::TableNextColumn()) {
377 ImGui::RadioButton(
tmpStrCat(
"##noPopup", std::to_underlying(level)).c_str(), &popupAction[level], NO_POPUP);
381 if (ImGui::TableNextColumn()) {
384 ImGui::TableNextRow();
385 if (ImGui::TableNextColumn()) {
388 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
389 if (ImGui::TableNextColumn()) {
390 ImGui::RadioButton(
tmpStrCat(
"##focus", std::to_underlying(level)).c_str(), &openLogAction[level], OPEN_LOG_FOCUS);
393 if (ImGui::TableNextColumn()) {
396 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
397 if (ImGui::TableNextColumn()) {
398 ImGui::RadioButton(
tmpStrCat(
"##log", std::to_underlying(level)).c_str(), &openLogAction[level], OPEN_LOG);
401 if (ImGui::TableNextColumn()) {
404 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
405 if (ImGui::TableNextColumn()) {
406 ImGui::RadioButton(
tmpStrCat(
"##nolog", std::to_underlying(level)).c_str(), &openLogAction[level], NO_OPEN_LOG);
410 if (ImGui::TableNextColumn()) {
413 ImGui::TableNextRow();
414 if (ImGui::TableNextColumn()) {
417 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
418 if (ImGui::TableNextColumn()) {
419 ImGui::RadioButton(
tmpStrCat(
"##osd", std::to_underlying(level)).c_str(), &osdAction[level], SHOW_OSD);
422 if (ImGui::TableNextColumn()) {
425 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
426 if (ImGui::TableNextColumn()) {
427 ImGui::RadioButton(
tmpStrCat(
"##no-osd", std::to_underlying(level)).c_str(), &osdAction[level], NO_OSD);
430 if (ImGui::TableNextColumn()) {
433 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
434 if (ImGui::TableNextColumn()) {
435 float& d = colorSequence[level][2].start;
436 if (ImGui::InputFloat(
tmpStrCat(
"##dur", std::to_underlying(level)).c_str(), &d, 0.0f, 0.0f,
"%.0f", ImGuiInputTextFlags_CharsDecimal)) {
437 d = std::clamp(d, 1.0f, 99.0f);
447 return ((buffer.
size() >= 5) && ((now - buffer[4].timestamp) < 1'000'000)) ||
448 ((buffer.
size() >= 10) && ((now - buffer[9].timestamp) < 60'000'000));
451void ImGuiMessages::log(
CliComm::LogLevel level, std::string_view text,
float fraction)
453 if (level == PROGRESS) {
454 progressMessage = text;
455 progressFraction = fraction;
456 if (progressFraction < 1.0f) doOpenProgress =
true;
461 Message message{level, std::string(text), now};
463 if (popupAction[level] == MODAL_POPUP) {
464 if (modalMessages.full()) modalMessages.pop_back();
465 modalMessages.push_front(message);
466 if (!needThrottle(modalMessages, now)) {
469 }
else if (popupAction[level] == POPUP) {
470 if (popupMessages.full()) popupMessages.pop_back();
471 popupMessages.push_front(message);
472 if (!needThrottle(popupMessages, now)) {
473 doOpenPopup = popupMessages.size();
477 if (openLogAction[level] == OPEN_LOG) {
479 }
else if (openLogAction[level] == OPEN_LOG_FOCUS) {
480 if (!needThrottle(allMessages, now)) {
485 if (osdAction[level] == SHOW_OSD) {
486 if (osdMessages.size() >= 10) {
487 osdMessages.erase(osdMessages.begin());
489 osdMessages.emplace_back(std::string(text), 0.0f, level);
492 if (allMessages.full()) allMessages.pop_back();
493 allMessages.push_front(std::move(message));
Circular buffer class, based on boost::circular_buffer/.
std::unique_ptr< CliListener > removeListener(CliListener &listener)
void loadLine(std::string_view name, zstring_view value) override
void save(ImGuiTextBuffer &buf) override
void paint(MSXMotherBoard *motherBoard) override
im::WindowStatus logWindow
ImGuiMessages(ImGuiManager &manager_)
im::WindowStatus configureWindow
GlobalCliComm & getGlobalCliComm()
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
auto CalcTextSize(std::string_view str)
void TextUnformatted(const std::string &str)
bool containsCaseInsensitive(std::string_view haystack, std::string_view needle)
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void StyleColor(bool active, Args &&...args)
void Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
void PopupModal(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void TextWrapPos(float wrap_local_pos_x, std::invocable<> auto next)
void Indent(float indent_w, std::invocable<> auto next)
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
uint64_t getTime()
Get current (real) time in us.
This file implemented 3 utility functions:
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void simpleToolTip(std::string_view desc)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
ImU32 getColor(imColor col)
constexpr bool all_of(InputRange &&range, UnaryPredicate pred)
size_t size(std::string_view utf8)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)