1 // Copyright 2009 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "VideoCommon/OnScreenDisplay.h"
6
7 #include <algorithm>
8 #include <atomic>
9 #include <map>
10 #include <mutex>
11 #include <string>
12
13 #include <fmt/format.h>
14 #include <imgui.h>
15
16 #include "Common/CommonTypes.h"
17 #include "Common/Config/Config.h"
18 #include "Common/Timer.h"
19
20 #include "Core/Config/MainSettings.h"
21
22 namespace OSD
23 {
24 constexpr float LEFT_MARGIN = 10.0f; // Pixels to the left of OSD messages.
25 constexpr float TOP_MARGIN = 10.0f; // Pixels above the first OSD message.
26 constexpr float WINDOW_PADDING = 4.0f; // Pixels between subsequent OSD messages.
27
28 static std::atomic<int> s_obscured_pixels_left = 0;
29 static std::atomic<int> s_obscured_pixels_top = 0;
30
31 struct Message
32 {
33 Message() = default;
MessageOSD::Message34 Message(std::string text_, u32 timestamp_, u32 color_)
35 : text(std::move(text_)), timestamp(timestamp_), color(color_)
36 {
37 }
38 std::string text;
39 u32 timestamp = 0;
40 u32 color = 0;
41 };
42 static std::multimap<MessageType, Message> s_messages;
43 static std::mutex s_messages_mutex;
44
ARGBToImVec4(const u32 argb)45 static ImVec4 ARGBToImVec4(const u32 argb)
46 {
47 return ImVec4(static_cast<float>((argb >> 16) & 0xFF) / 255.0f,
48 static_cast<float>((argb >> 8) & 0xFF) / 255.0f,
49 static_cast<float>((argb >> 0) & 0xFF) / 255.0f,
50 static_cast<float>((argb >> 24) & 0xFF) / 255.0f);
51 }
52
DrawMessage(int index,const Message & msg,const ImVec2 & position,int time_left)53 static float DrawMessage(int index, const Message& msg, const ImVec2& position, int time_left)
54 {
55 // We have to provide a window name, and these shouldn't be duplicated.
56 // So instead, we generate a name based on the number of messages drawn.
57 const std::string window_name = fmt::format("osd_{}", index);
58
59 // The size must be reset, otherwise the length of old messages could influence new ones.
60 ImGui::SetNextWindowPos(position);
61 ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
62
63 // Gradually fade old messages away.
64 const float alpha = std::min(1.0f, std::max(0.0f, time_left / 1024.0f));
65 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
66
67 float window_height = 0.0f;
68 if (ImGui::Begin(window_name.c_str(), nullptr,
69 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
70 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
71 ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
72 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
73 {
74 // Use %s in case message contains %.
75 ImGui::TextColored(ARGBToImVec4(msg.color), "%s", msg.text.c_str());
76 window_height =
77 ImGui::GetWindowSize().y + (WINDOW_PADDING * ImGui::GetIO().DisplayFramebufferScale.y);
78 }
79
80 ImGui::End();
81 ImGui::PopStyleVar();
82
83 return window_height;
84 }
85
AddTypedMessage(MessageType type,std::string message,u32 ms,u32 argb)86 void AddTypedMessage(MessageType type, std::string message, u32 ms, u32 argb)
87 {
88 std::lock_guard lock{s_messages_mutex};
89 s_messages.erase(type);
90 s_messages.emplace(type, Message(std::move(message), Common::Timer::GetTimeMs() + ms, argb));
91 }
92
AddMessage(std::string message,u32 ms,u32 argb)93 void AddMessage(std::string message, u32 ms, u32 argb)
94 {
95 std::lock_guard lock{s_messages_mutex};
96 s_messages.emplace(MessageType::Typeless,
97 Message(std::move(message), Common::Timer::GetTimeMs() + ms, argb));
98 }
99
DrawMessages()100 void DrawMessages()
101 {
102 const bool draw_messages = Config::Get(Config::MAIN_OSD_MESSAGES);
103 const u32 now = Common::Timer::GetTimeMs();
104 const float current_x =
105 LEFT_MARGIN * ImGui::GetIO().DisplayFramebufferScale.x + s_obscured_pixels_left;
106 float current_y = TOP_MARGIN * ImGui::GetIO().DisplayFramebufferScale.y + s_obscured_pixels_top;
107 int index = 0;
108
109 std::lock_guard lock{s_messages_mutex};
110
111 for (auto it = s_messages.begin(); it != s_messages.end();)
112 {
113 const Message& msg = it->second;
114 const int time_left = static_cast<int>(msg.timestamp - now);
115
116 if (time_left <= 0)
117 {
118 it = s_messages.erase(it);
119 continue;
120 }
121 else
122 {
123 ++it;
124 }
125
126 if (draw_messages)
127 current_y += DrawMessage(index++, msg, ImVec2(current_x, current_y), time_left);
128 }
129 }
130
ClearMessages()131 void ClearMessages()
132 {
133 std::lock_guard lock{s_messages_mutex};
134 s_messages.clear();
135 }
136
SetObscuredPixelsLeft(int width)137 void SetObscuredPixelsLeft(int width)
138 {
139 s_obscured_pixels_left = width;
140 }
141
SetObscuredPixelsTop(int height)142 void SetObscuredPixelsTop(int height)
143 {
144 s_obscured_pixels_top = height;
145 }
146
147 } // namespace OSD
148