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