15#include <imgui_stdlib.h>
23using namespace std::literals;
32 popupAction[INFO] = NO_POPUP;
34 popupAction[LOGLEVEL_ERROR] = MODAL_POPUP;
35 popupAction[PROGRESS] = NO_POPUP;
36 openLogAction[INFO] = NO_OPEN_LOG;
37 openLogAction[
WARNING] = NO_OPEN_LOG;
38 openLogAction[LOGLEVEL_ERROR] = NO_OPEN_LOG;
39 openLogAction[PROGRESS] = NO_OPEN_LOG;
40 osdAction[INFO] = SHOW_OSD;
42 osdAction[LOGLEVEL_ERROR] = NO_OSD;
43 osdAction[PROGRESS] = NO_OSD;
47 listenerHandle = cliComm.addListener(std::make_unique<Listener>(*
this));
60 buf.appendf(
"popupActions=[%d %d %d]\n",
61 popupAction[LOGLEVEL_ERROR],
64 buf.appendf(
"openLogActions=[%d %d %d]\n",
65 openLogAction[LOGLEVEL_ERROR],
68 buf.appendf(
"osdActions=[%d %d %d]\n",
69 osdAction[LOGLEVEL_ERROR],
72 buf.appendf(
"fadeOutDuration=[%f %f %f]\n",
73 double(colorSequence[LOGLEVEL_ERROR][2].start),
74 double(colorSequence[
WARNING][2].start),
75 double(colorSequence[INFO][2].start));
82 }
else if (name ==
"popupActions"sv) {
83 std::array<int, 3> a = {};
84 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
85 popupAction[LOGLEVEL_ERROR] = PopupAction(a[0]);
86 popupAction[
WARNING] = PopupAction(a[1]);
87 popupAction[INFO] = PopupAction(a[2]);
89 }
else if (name ==
"openLogActions"sv) {
90 std::array<int, 3> a = {};
91 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
92 openLogAction[LOGLEVEL_ERROR] = OpenLogAction(a[0]);
93 openLogAction[
WARNING] = OpenLogAction(a[1]);
94 openLogAction[INFO] = OpenLogAction(a[2]);
96 }
else if (name ==
"osdActions"sv) {
97 std::array<int, 3> a = {};
98 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
99 osdAction[LOGLEVEL_ERROR] = OsdAction(a[0]);
100 osdAction[
WARNING] = OsdAction(a[1]);
101 osdAction[INFO] = OsdAction(a[2]);
103 }
else if (name ==
"fadeOutDuration"sv) {
104 std::array<float, 3> a = {};
105 if (sscanf(value.
c_str(),
"[%f %f %f]", &a[0], &a[1], &a[2]) == 3) {
106 colorSequence[LOGLEVEL_ERROR][2].start = a[0];
107 colorSequence[
WARNING][2].start = a[1];
108 colorSequence[INFO][2].start = a[2];
123template<std::predicate<std::
string_view, std::
string_view> Filter = always_true>
127 for (
const auto& message : messages) {
128 auto [color, prefix_] = [&]() -> std::pair<ImU32, std::string_view> {
129 switch (message.level) {
136 auto prefix = prefix_;
137 if (std::invoke(filter, prefix, message.text)) {
148bool ImGuiMessages::paintButtons()
150 ImGui::SetCursorPosX(40.0f);
151 bool close = ImGui::Button(
"Ok");
152 ImGui::SameLine(0.0f, 30.0f);
153 if (ImGui::SmallButton(
"Configure...")) {
160void ImGuiMessages::paintModal()
164 ImGui::OpenPopup(
"Message");
168 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, {0.5f, 0.5f});
169 im::PopupModal(
"Message", &open, ImGuiWindowFlags_AlwaysAutoResize, [&]{
170 printMessages(modalMessages);
171 bool close = paintButtons();
172 if (!open || close) {
173 modalMessages.clear();
174 ImGui::CloseCurrentPopup();
179void ImGuiMessages::paintPopup()
184 if (!ImGui::IsPopupOpen(
"popup-message")) {
185 while (popupMessages.size() > doOpenPopup) {
186 popupMessages.pop_back();
190 ImGui::OpenPopup(
"popup-message");
194 printMessages(popupMessages);
195 bool close = paintButtons();
196 if (close) ImGui::CloseCurrentPopup();
200void ImGuiMessages::paintProgress()
202 if (doOpenProgress) {
203 doOpenProgress =
false;
204 ImGui::OpenPopup(
"popup-progress");
208 if (progressFraction >= 1.0f) {
209 ImGui::CloseCurrentPopup();
212 if (progressFraction >= 0.0f) {
213 ImGui::ProgressBar(progressFraction);
216 progressTime = fmodf(progressTime + ImGui::GetIO().DeltaTime, 2.0f);
217 float fraction = (progressTime < 1.0f) ? progressTime : (2.0f - progressTime);
218 ImGui::ProgressBar(fraction, {},
"");
224void ImGuiMessages::paintOSD()
226 auto getColors = [&](
const ColorSequence& seq,
float t) -> std::optional<Colors> {
228 const auto& step0 = seq[i + 0];
229 const auto& step1 = seq[i + 1];
230 if (
t < step1.start) {
232 auto x = int(256.0f * (
t / step1.start));
233 auto tCol = p.lerp(step0.colors.text, step1.colors.text, x);
234 auto bCol = p.lerp(step0.colors.background, step1.colors.background, x);
235 return Colors{tCol, bCol};
242 const auto& style = ImGui::GetStyle();
243 gl::vec2 offset = style.FramePadding;
244 const auto* mainViewPort = ImGui::GetMainViewport();
248 DrawInfo(std::string m,
gl::vec2 s,
float y, uint32_t
t, uint32_t b)
249 : message(
std::move(m)), boxSize(s), yPos(y), textCol(
t), bgCol(b) {}
254 uint32_t textCol, bgCol;
256 std::vector<DrawInfo> drawInfo;
260 float delta = ImGui::GetIO().DeltaTime;
261 std::erase_if(osdMessages, [&](OsdMessage& message) {
262 message.time += delta;
263 auto colors = getColors(colorSequence[message.level], message.time);
264 if (!colors)
return true;
266 auto& text = message.text;
268 gl::vec2 boxSize = textSize + 2.0f * offset;
269 drawInfo.emplace_back(text, boxSize, y, colors->text, colors->background);
270 y += boxSize.y + style.ItemSpacing.y;
271 width = std::max(width, boxSize.x);
274 if (drawInfo.empty())
return;
276 int flags = ImGuiWindowFlags_NoMove
277 | ImGuiWindowFlags_NoBackground
278 | ImGuiWindowFlags_NoSavedSettings
279 | ImGuiWindowFlags_NoDocking
280 | ImGuiWindowFlags_NoNav
281 | ImGuiWindowFlags_NoDecoration
282 | ImGuiWindowFlags_NoInputs
283 | ImGuiWindowFlags_NoFocusOnAppearing;
284 ImGui::SetNextWindowViewport(mainViewPort->ID);
285 ImGui::SetNextWindowPos(
gl::vec2(mainViewPort->WorkPos) +
gl::vec2(style.ItemSpacing));
286 ImGui::SetNextWindowSize({width, y});
287 im::Window(
"OSD messages",
nullptr, flags, [&]{
288 auto* drawList = ImGui::GetWindowDrawList();
289 gl::vec2 windowPos = ImGui::GetWindowPos();
290 for (
const auto& [message, boxSize, yPos, textCol, bgCol] : drawInfo) {
292 drawList->AddRectFilled(pos, pos + boxSize, bgCol);
293 drawList->AddText(pos + offset, textCol, message.data(), message.data() + message.size());
298void ImGuiMessages::paintLog()
302 ImGui::SetNextWindowFocus();
304 ImGui::SetNextWindowSize(
gl::vec2{40, 14} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
306 const auto& style = ImGui::GetStyle();
307 auto buttonHeight = ImGui::GetFontSize() + 2.0f * style.FramePadding.y + style.ItemSpacing.y;
308 im::Child(
"messages", {0.0f, -buttonHeight}, ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar, [&]{
309 printMessages(allMessages, [&](std::string_view prefix, std::string_view message) {
310 if (filterLog.empty())
return true;
312 return ranges::all_of(StringOp::split_view<StringOp::EmptyParts::REMOVE>(filterLog,
' '),
316 if (ImGui::Button(
"Clear")) {
320 ImGui::SameLine(0.0f, 30.0f);
324 ImGui::SetNextItemWidth(-size);
325 ImGui::InputTextWithHint(
"##filter",
"enter filter terms", &filterLog);
326 ImGui::SameLine(0.0f, 30.0f);
327 if (ImGui::SmallButton(
"Configure...")) {
333void ImGuiMessages::paintConfigure()
335 ImGui::SetNextWindowSize(
gl::vec2{24, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
340 im::Table(
"table", 4, ImGuiTableFlags_SizingFixedFit, [&]{
341 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_None, 0);
342 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
343 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
344 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
346 if (ImGui::TableNextColumn()) { }
351 if (ImGui::TableNextColumn()) {
354 ImGui::TableNextRow();
355 if (ImGui::TableNextColumn()) {
358 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
359 if (ImGui::TableNextColumn()) {
363 if (ImGui::TableNextColumn()) {
366 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
367 if (ImGui::TableNextColumn()) {
371 if (ImGui::TableNextColumn()) {
374 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
375 if (ImGui::TableNextColumn()) {
380 if (ImGui::TableNextColumn()) {
383 ImGui::TableNextRow();
384 if (ImGui::TableNextColumn()) {
387 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
388 if (ImGui::TableNextColumn()) {
389 ImGui::RadioButton(
tmpStrCat(
"##focus",
to_underlying(level)).c_str(), &openLogAction[level], OPEN_LOG_FOCUS);
392 if (ImGui::TableNextColumn()) {
395 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
396 if (ImGui::TableNextColumn()) {
400 if (ImGui::TableNextColumn()) {
403 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
404 if (ImGui::TableNextColumn()) {
405 ImGui::RadioButton(
tmpStrCat(
"##nolog",
to_underlying(level)).c_str(), &openLogAction[level], NO_OPEN_LOG);
409 if (ImGui::TableNextColumn()) {
412 ImGui::TableNextRow();
413 if (ImGui::TableNextColumn()) {
416 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
417 if (ImGui::TableNextColumn()) {
421 if (ImGui::TableNextColumn()) {
424 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
425 if (ImGui::TableNextColumn()) {
429 if (ImGui::TableNextColumn()) {
432 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
433 if (ImGui::TableNextColumn()) {
434 float& d = colorSequence[level][2].start;
435 if (ImGui::InputFloat(
tmpStrCat(
"##dur",
to_underlying(level)).c_str(), &d, 0.0f, 0.0f,
"%.0f", ImGuiInputTextFlags_CharsDecimal)) {
436 d = std::clamp(d, 1.0f, 99.0f);
446 return ((buffer.
size() >= 5) && ((now - buffer[4].timestamp) < 1'000'000)) ||
447 ((buffer.
size() >= 10) && ((now - buffer[9].timestamp) < 60'000'000));
450void ImGuiMessages::log(
CliComm::LogLevel level, std::string_view text,
float fraction)
452 if (level == PROGRESS) {
453 progressMessage = text;
454 progressFraction = fraction;
455 if (progressFraction < 1.0f) doOpenProgress =
true;
460 Message message{level, std::string(text), now};
462 if (popupAction[level] == MODAL_POPUP) {
463 if (modalMessages.full()) modalMessages.pop_back();
464 modalMessages.push_front(message);
465 if (!needThrottle(modalMessages, now)) {
468 }
else if (popupAction[level] == POPUP) {
469 if (popupMessages.full()) popupMessages.pop_back();
470 popupMessages.push_front(message);
471 if (!needThrottle(popupMessages, now)) {
472 doOpenPopup = popupMessages.size();
476 if (openLogAction[level] == OPEN_LOG) {
478 }
else if (openLogAction[level] == OPEN_LOG_FOCUS) {
479 if (!needThrottle(allMessages, now)) {
484 if (osdAction[level] == SHOW_OSD) {
485 if (osdMessages.size() >= 10) {
486 osdMessages.erase(osdMessages.begin());
488 osdMessages.emplace_back(std::string(text), 0.0f, level);
491 if (allMessages.full()) allMessages.pop_back();
492 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)
constexpr auto to_underlying(E e) noexcept
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)