14#include <imgui_stdlib.h>
22using namespace std::literals;
31 popupAction[INFO] = NO_POPUP;
33 popupAction[LOGLEVEL_ERROR] = MODAL_POPUP;
34 popupAction[PROGRESS] = NO_POPUP;
35 openLogAction[INFO] = NO_OPEN_LOG;
36 openLogAction[
WARNING] = NO_OPEN_LOG;
37 openLogAction[LOGLEVEL_ERROR] = NO_OPEN_LOG;
38 openLogAction[PROGRESS] = NO_OPEN_LOG;
39 osdAction[INFO] = SHOW_OSD;
41 osdAction[LOGLEVEL_ERROR] = NO_OSD;
42 osdAction[PROGRESS] = NO_OSD;
46 listenerHandle = cliComm.addListener(std::make_unique<Listener>(*
this));
59 buf.appendf(
"popupActions=[%d %d %d]\n",
60 popupAction[LOGLEVEL_ERROR],
63 buf.appendf(
"openLogActions=[%d %d %d]\n",
64 openLogAction[LOGLEVEL_ERROR],
67 buf.appendf(
"osdActions=[%d %d %d]\n",
68 osdAction[LOGLEVEL_ERROR],
71 buf.appendf(
"fadeOutDuration=[%f %f %f]\n",
72 double(colorSequence[LOGLEVEL_ERROR][2].start),
73 double(colorSequence[
WARNING][2].start),
74 double(colorSequence[INFO][2].start));
81 }
else if (name ==
"popupActions"sv) {
82 std::array<int, 3> a = {};
83 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
84 popupAction[LOGLEVEL_ERROR] = PopupAction(a[0]);
85 popupAction[
WARNING] = PopupAction(a[1]);
86 popupAction[INFO] = PopupAction(a[2]);
88 }
else if (name ==
"openLogActions"sv) {
89 std::array<int, 3> a = {};
90 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
91 openLogAction[LOGLEVEL_ERROR] = OpenLogAction(a[0]);
92 openLogAction[
WARNING] = OpenLogAction(a[1]);
93 openLogAction[INFO] = OpenLogAction(a[2]);
95 }
else if (name ==
"osdActions"sv) {
96 std::array<int, 3> a = {};
97 if (sscanf(value.
c_str(),
"[%d %d %d]", &a[0], &a[1], &a[2]) == 3) {
98 osdAction[LOGLEVEL_ERROR] = OsdAction(a[0]);
99 osdAction[
WARNING] = OsdAction(a[1]);
100 osdAction[INFO] = OsdAction(a[2]);
102 }
else if (name ==
"fadeOutDuration"sv) {
103 std::array<float, 3> a = {};
104 if (sscanf(value.
c_str(),
"[%f %f %f]", &a[0], &a[1], &a[2]) == 3) {
105 colorSequence[LOGLEVEL_ERROR][2].start = a[0];
106 colorSequence[
WARNING][2].start = a[1];
107 colorSequence[INFO][2].start = a[2];
122template<std::predicate<std::
string_view, std::
string_view> Filter = always_true>
126 for (
const auto& message : messages) {
127 auto [color, prefix_] = [&]() -> std::pair<ImU32, std::string_view> {
128 switch (message.level) {
135 auto prefix = prefix_;
136 if (std::invoke(filter, prefix, message.text)) {
147bool ImGuiMessages::paintButtons()
149 ImGui::SetCursorPosX(40.0f);
150 bool close = ImGui::Button(
"Ok");
151 ImGui::SameLine(0.0f, 30.0f);
152 if (ImGui::SmallButton(
"Configure...")) {
159void ImGuiMessages::paintModal()
163 ImGui::OpenPopup(
"Message");
167 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, {0.5f, 0.5f});
168 im::PopupModal(
"Message", &open, ImGuiWindowFlags_AlwaysAutoResize, [&]{
169 printMessages(modalMessages);
170 bool close = paintButtons();
171 if (!open || close) {
172 modalMessages.clear();
173 ImGui::CloseCurrentPopup();
178void ImGuiMessages::paintPopup()
183 if (!ImGui::IsPopupOpen(
"popup-message")) {
184 while (popupMessages.size() > doOpenPopup) {
185 popupMessages.pop_back();
189 ImGui::OpenPopup(
"popup-message");
193 printMessages(popupMessages);
194 bool close = paintButtons();
195 if (close) ImGui::CloseCurrentPopup();
199void ImGuiMessages::paintProgress()
201 if (doOpenProgress) {
202 doOpenProgress =
false;
203 ImGui::OpenPopup(
"popup-progress");
207 if (progressFraction >= 1.0f) {
208 ImGui::CloseCurrentPopup();
211 if (progressFraction >= 0.0f) {
212 ImGui::ProgressBar(progressFraction);
215 progressTime = fmodf(progressTime + ImGui::GetIO().DeltaTime, 2.0f);
216 float fraction = (progressTime < 1.0f) ? progressTime : (2.0f - progressTime);
217 ImGui::ProgressBar(fraction, {},
"");
223void ImGuiMessages::paintOSD()
225 auto getColors = [&](
const ColorSequence& seq,
float t) -> std::optional<Colors> {
227 const auto& step0 = seq[i + 0];
228 const auto& step1 = seq[i + 1];
229 if (
t < step1.start) {
231 auto x = int(256.0f * (
t / step1.start));
232 auto tCol = p.lerp(step0.colors.text, step1.colors.text, x);
233 auto bCol = p.lerp(step0.colors.background, step1.colors.background, x);
234 return Colors{tCol, bCol};
241 const auto& style = ImGui::GetStyle();
242 gl::vec2 offset = style.FramePadding;
243 const auto* mainViewPort = ImGui::GetMainViewport();
247 DrawInfo(
const std::string& m,
gl::vec2 s,
float y, uint32_t
t, uint32_t b)
248 : message(m), boxSize(s), yPos(y), textCol(
t), bgCol(b) {}
253 uint32_t textCol, bgCol;
255 std::vector<DrawInfo> drawInfo;
259 float delta = ImGui::GetIO().DeltaTime;
260 std::erase_if(osdMessages, [&](OsdMessage& message) {
261 message.time += delta;
262 auto colors = getColors(colorSequence[message.level], message.time);
263 if (!colors)
return true;
265 auto& text = message.text;
267 gl::vec2 boxSize = textSize + 2.0f * offset;
268 drawInfo.emplace_back(text, boxSize, y, colors->text, colors->background);
269 y += boxSize.y + style.ItemSpacing.y;
270 width = std::max(width, boxSize.x);
273 if (drawInfo.empty())
return;
275 int flags = ImGuiWindowFlags_NoMove
276 | ImGuiWindowFlags_NoBackground
277 | ImGuiWindowFlags_NoSavedSettings
278 | ImGuiWindowFlags_NoDocking
279 | ImGuiWindowFlags_NoNav
280 | ImGuiWindowFlags_NoDecoration
281 | ImGuiWindowFlags_NoInputs
282 | ImGuiWindowFlags_NoFocusOnAppearing;
283 ImGui::SetNextWindowViewport(mainViewPort->ID);
284 ImGui::SetNextWindowPos(
gl::vec2(mainViewPort->WorkPos) +
gl::vec2(style.ItemSpacing));
285 ImGui::SetNextWindowSize({width, y});
286 im::Window(
"OSD messages",
nullptr, flags, [&]{
287 auto* drawList = ImGui::GetWindowDrawList();
288 gl::vec2 windowPos = ImGui::GetWindowPos();
289 for (
const auto& [message, boxSize, yPos, textCol, bgCol] : drawInfo) {
291 drawList->AddRectFilled(pos, pos + boxSize, bgCol);
292 drawList->AddText(pos + offset, textCol, message.data(), message.data() + message.size());
297void ImGuiMessages::paintLog()
301 ImGui::SetNextWindowFocus();
303 ImGui::SetNextWindowSize(
gl::vec2{40, 14} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
305 const auto& style = ImGui::GetStyle();
306 auto buttonHeight = ImGui::GetFontSize() + 2.0f * style.FramePadding.y + style.ItemSpacing.y;
307 im::Child(
"messages", {0.0f, -buttonHeight}, ImGuiChildFlags_Border, ImGuiWindowFlags_HorizontalScrollbar, [&]{
308 printMessages(allMessages, [&](std::string_view prefix, std::string_view message) {
309 if (filterLog.empty())
return true;
311 return ranges::all_of(StringOp::split_view<StringOp::EmptyParts::REMOVE>(filterLog,
' '),
315 if (ImGui::Button(
"Clear")) {
319 ImGui::SameLine(0.0f, 30.0f);
323 ImGui::SetNextItemWidth(-size);
324 ImGui::InputTextWithHint(
"##filter",
"enter filter terms", &filterLog);
325 ImGui::SameLine(0.0f, 30.0f);
326 if (ImGui::SmallButton(
"Configure...")) {
332void ImGuiMessages::paintConfigure()
334 ImGui::SetNextWindowSize(
gl::vec2{24, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
339 im::Table(
"table", 4, ImGuiTableFlags_SizingFixedFit, [&]{
340 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_None, 0);
341 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
342 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
343 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed, size);
345 if (ImGui::TableNextColumn()) { }
350 if (ImGui::TableNextColumn()) {
353 ImGui::TableNextRow();
354 if (ImGui::TableNextColumn()) {
357 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
358 if (ImGui::TableNextColumn()) {
362 if (ImGui::TableNextColumn()) {
365 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
366 if (ImGui::TableNextColumn()) {
370 if (ImGui::TableNextColumn()) {
373 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
374 if (ImGui::TableNextColumn()) {
379 if (ImGui::TableNextColumn()) {
382 ImGui::TableNextRow();
383 if (ImGui::TableNextColumn()) {
386 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
387 if (ImGui::TableNextColumn()) {
388 ImGui::RadioButton(
tmpStrCat(
"##focus",
to_underlying(level)).c_str(), &openLogAction[level], OPEN_LOG_FOCUS);
391 if (ImGui::TableNextColumn()) {
394 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
395 if (ImGui::TableNextColumn()) {
399 if (ImGui::TableNextColumn()) {
402 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
403 if (ImGui::TableNextColumn()) {
404 ImGui::RadioButton(
tmpStrCat(
"##nolog",
to_underlying(level)).c_str(), &openLogAction[level], NO_OPEN_LOG);
408 if (ImGui::TableNextColumn()) {
411 ImGui::TableNextRow();
412 if (ImGui::TableNextColumn()) {
415 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
416 if (ImGui::TableNextColumn()) {
420 if (ImGui::TableNextColumn()) {
423 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
424 if (ImGui::TableNextColumn()) {
428 if (ImGui::TableNextColumn()) {
431 for (
auto level : {LOGLEVEL_ERROR,
WARNING, INFO}) {
432 if (ImGui::TableNextColumn()) {
433 float& d = colorSequence[level][2].start;
434 if (ImGui::InputFloat(
tmpStrCat(
"##dur",
to_underlying(level)).c_str(), &d, 0.0f, 0.0f,
"%.0f", ImGuiInputTextFlags_CharsDecimal)) {
435 d = std::clamp(d, 1.0f, 99.0f);
443void ImGuiMessages::log(
CliComm::LogLevel level, std::string_view text,
float fraction)
445 if (level == PROGRESS) {
446 progressMessage = text;
447 progressFraction = fraction;
448 if (progressFraction < 1.0f) doOpenProgress =
true;
452 Message message{level, std::string(text)};
454 if (popupAction[level] == MODAL_POPUP) {
455 if (modalMessages.full()) modalMessages.pop_back();
456 modalMessages.push_front(message);
458 }
else if (popupAction[level] == POPUP) {
459 if (popupMessages.full()) popupMessages.pop_back();
460 popupMessages.push_front(message);
461 doOpenPopup = popupMessages.size();
464 if (openLogAction[level] == OPEN_LOG) {
466 }
else if (openLogAction[level] == OPEN_LOG_FOCUS) {
470 if (osdAction[level] == SHOW_OSD) {
471 osdMessages.emplace_back(std::string(text), 0.0f, level);
474 if (allMessages.full()) allMessages.pop_back();
475 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)
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)