16#include <imgui_stdlib.h>
20using namespace std::literals;
37 for (
const auto& [i, icon] :
enumerate(iconInfo)) {
38 auto n = narrow<int>(i + 1);
39 buf.appendf(
"icon.%d.enabled=%d\n", n, icon.enable);
40 buf.appendf(
"icon.%d.fade=%d\n", n, icon.fade);
41 buf.appendf(
"icon.%d.on-image=%s\n", n, icon.on.filename.c_str());
42 buf.appendf(
"icon.%d.off-image=%s\n", n, icon.off.filename.c_str());
43 buf.appendf(
"icon.%d.expr=%s\n", n, icon.expr.getString().c_str());
56 }
else if (adjust.
loadLine(name, value)) {
58 }
else if (name.starts_with(
"icon.")) {
60 auto n = StringOp::stringTo<size_t>(numStr);
61 if (!n || *n == 0)
return;
62 while (iconInfo.size() < *n) {
63 iconInfo.emplace_back();
65 auto& info = iconInfo[*n - 1];
66 if (suffix ==
"enabled") {
68 }
else if (suffix ==
"fade") {
70 }
else if (suffix ==
"on-image") {
71 info.on.filename = value;
72 }
else if (suffix ==
"off-image") {
73 info.off.filename = value;
74 }
else if (suffix ==
"expr") {
82 if (iconInfo.empty()) setDefaultIcons();
86void ImGuiOsdIcons::setDefaultIcons()
89 iconInfo.emplace_back(
TclObject(
"$led_power"),
"skins/set1/power-on.png",
"skins/set1/power-off.png",
true);
90 iconInfo.emplace_back(
TclObject(
"$led_caps" ),
"skins/set1/caps-on.png",
"skins/set1/caps-off.png",
true);
91 iconInfo.emplace_back(
TclObject(
"$led_kana" ),
"skins/set1/kana-on.png",
"skins/set1/kana-off.png",
true);
92 iconInfo.emplace_back(
TclObject(
"$led_pause"),
"skins/set1/pause-on.png",
"skins/set1/pause-off.png",
true);
93 iconInfo.emplace_back(
TclObject(
"$led_turbo"),
"skins/set1/turbo-on.png",
"skins/set1/turbo-off.png",
true);
94 iconInfo.emplace_back(
TclObject(
"$led_FDD" ),
"skins/set1/fdd-on.png",
"skins/set1/fdd-off.png",
true);
95 iconInfo.emplace_back(
TclObject(
"$pause" ),
"skins/set1/pause.png",
"",
false);
96 iconInfo.emplace_back(
TclObject(
"!$throttle || $fastforward"),
"skins/set1/throttle.png",
"",
false);
97 iconInfo.emplace_back(
TclObject(
"$mute" ),
"skins/set1/mute.png",
"",
false);
98 iconInfo.emplace_back(
TclObject(
"$breaked" ),
"skins/set1/breaked.png",
"",
false);
102void ImGuiOsdIcons::loadIcons()
107 for (
auto& icon : iconInfo) {
108 if (!icon.enable)
continue;
110 auto load = [&](IconInfo::Icon& i) {
112 if (!i.filename.empty()) {
113 i.tex = loadTexture(context.resolve(i.filename), i.size);
125 auto m =
max(icon.on.size, icon.off.size);
130 iconInfoDirty =
false;
135 if (iconInfoDirty) loadIcons();
140 const auto* mainViewPort = ImGui::GetMainViewport();
141 ImGui::SetNextWindowPos(
gl::vec2(mainViewPort->Pos) +
gl::vec2{10.0f, mainViewPort->WorkSize.y - 10.0f},
142 ImGuiCond_FirstUseEver,
144 int flags = hideTitle ? ImGuiWindowFlags_NoTitleBar |
145 ImGuiWindowFlags_NoResize |
146 ImGuiWindowFlags_NoScrollbar |
147 ImGuiWindowFlags_NoScrollWithMouse |
148 ImGuiWindowFlags_NoCollapse |
149 ImGuiWindowFlags_NoBackground |
150 ImGuiWindowFlags_NoFocusOnAppearing |
151 ImGuiWindowFlags_NoNav |
152 (allowMove ? 0 : ImGuiWindowFlags_NoMove)
156 bool isOnMainViewPort = adjust.
post();
157 gl::vec2 topLeft = ImGui::GetCursorPos();
159 const auto& style = ImGui::GetStyle();
160 const auto& io = ImGui::GetIO();
162 auto availableSize = ImGui::GetContentRegionAvail();
163 auto columns = std::max(
int(floor((availableSize.x + style.ItemSpacing.x) / (maxIconSize.x + style.ItemSpacing.x))), 1);
164 auto rows = (numIcons + columns - 1) / columns;
167 ImGui::Dummy(
gl::vec2(
float(columns) * maxIconSize.x +
float(columns - 1) * style.ItemSpacing.x,
168 float(rows ) * maxIconSize.y +
float(rows - 1) * style.ItemSpacing.y));
171 if (ImGui::MenuItem(
"Configure icons ...")) {
177 bool fade = hideTitle && !ImGui::IsWindowDocked() && isOnMainViewPort;
179 auto cursor = topLeft;
181 for (
auto& icon : iconInfo) {
182 if (!icon.enable)
continue;
192 if (state != icon.lastState) {
193 icon.lastState = state;
196 icon.time += io.DeltaTime;
200 if (!fade || !icon.fade)
return 1.0f;
201 auto t = icon.time - fadeDelay;
202 if (
t <= 0.0f)
return 1.0f;
203 if (
t >= fadeDuration)
return 0.0f;
204 return 1.0f - (
t / fadeDuration);
208 const auto& ic = state ? icon.on : icon.off;
209 if (alpha > 0.0f && ic.tex.get()) {
210 ImGui::SetCursorPos(cursor);
211 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size),
212 {0.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, alpha});
217 ImGui::SetCursorPos(cursor);
218 gl::vec2 rectMin = ImGui::GetCursorScreenPos();
220 ImGui::GetWindowDrawList()->AddRect(rectMin, rectMax, IM_COL32(255, 0, 0, 128), 0.0f, 0, 1.0f);
224 if (++col == columns) {
226 cursor =
gl::vec2(topLeft.x, cursor.y + style.ItemSpacing.y + maxIconSize.y);
228 cursor =
gl::vec2(cursor.x + style.ItemSpacing.x + maxIconSize.x, cursor.y);
232 if (hideTitle && ImGui::IsWindowFocused()) {
233 ImGui::SetWindowFocus(
nullptr);
238void ImGuiOsdIcons::paintConfigureIcons()
240 ImGui::SetNextWindowSize(
gl::vec2{37, 17} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
242 ImGui::Checkbox(
"Show OSD icons", &
showIcons);
245 if (ImGui::Checkbox(
"Hide Title", &hideTitle)) {
247 for (
auto& icon : iconInfo) {
251 HelpMarker(
"When you want the icons inside the MSX window, you might want to hide the window title.\n"
252 "To further hide the icons, it's possible to make them fade-out after some time.");
255 ImGui::Checkbox(
"Allow move", &allowMove);
256 HelpMarker(
"When the icons are in the MSX window (without title bar), you might want "
257 "to lock them in place, to prevent them from being moved by accident.\n"
258 "Move by click and drag the icon box.\n");
259 auto width = ImGui::GetFontSize() * 10.0f;
260 ImGui::SetNextItemWidth(width);
261 ImGui::SliderFloat(
"Fade-out delay", &fadeDelay, 0.0f, 30.0f,
"%.1f");
262 HelpMarker(
"After some delay, fade-out icons that haven't changed status for a while.\n"
263 "Note: by default some icons are configured to never fade-out.");
264 ImGui::SetNextItemWidth(width);
265 ImGui::SliderFloat(
"Fade-out duration", &fadeDuration, 0.0f, 30.0f,
"%.1f");
272 HelpMarker(
"Change the order and properties of the icons (for advanced users).\n"
273 "Right-click in a row to reorder, insert, delete that row.\n"
274 "Right-click on an icon to remove it.");
275 int flags = ImGuiTableFlags_RowBg |
276 ImGuiTableFlags_BordersOuterH |
277 ImGuiTableFlags_BordersInnerH |
278 ImGuiTableFlags_BordersV |
279 ImGuiTableFlags_BordersOuter |
280 ImGuiTableFlags_Resizable;
282 ImGui::TableSetupColumn(
"Enabled", ImGuiTableColumnFlags_WidthFixed);
283 ImGui::TableSetupColumn(
"Fade-out", ImGuiTableColumnFlags_WidthFixed);
284 ImGui::TableSetupColumn(
"True-image", ImGuiTableColumnFlags_WidthFixed);
285 ImGui::TableSetupColumn(
"False-image", ImGuiTableColumnFlags_WidthFixed);
286 ImGui::TableSetupColumn(
"Expression");
287 ImGui::TableHeadersRow();
289 enum class Cmd { MOVE_FRONT, MOVE_FWD, MOVE_BWD, MOVE_BACK, INSERT, DELETE };
291 std::pair<int, Cmd> cmd(-1, MOVE_FRONT);
292 auto lastRow = narrow<int>(iconInfo.size()) - 1;
294 auto& icon = iconInfo[row];
295 if (ImGui::TableNextColumn()) {
296 auto pos = ImGui::GetCursorPos();
297 const auto& style = ImGui::GetStyle();
298 auto textHeight = ImGui::GetTextLineHeight();
299 float rowHeight = std::max({
300 2.0f * style.FramePadding.y + textHeight,
301 float(icon.on.size.y),
302 float(icon.off.size.y)});
303 ImGui::Selectable(
"##row", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight));
304 if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
305 ImGui::OpenPopup(
"config-icon-context");
307 im::Popup(
"config-icon-context", [&]{
310 if (ImGui::MenuItem(
"Move to front")) cmd = {row, MOVE_FRONT};
311 if (ImGui::MenuItem(
"Move forwards")) cmd = {row, MOVE_FWD};
313 if (row != lastRow) {
314 if (ImGui::MenuItem(
"Move backwards"))cmd = {row, MOVE_BWD};
315 if (ImGui::MenuItem(
"Move to back")) cmd = {row, MOVE_BACK};
319 if (ImGui::MenuItem(
"Insert new row")) cmd = {row, INSERT};
320 if (ImGui::MenuItem(
"Delete current row")) cmd = {row, DELETE};
323 ImGui::SetCursorPos(pos);
324 if (ImGui::Checkbox(
"##enabled", &icon.enable)) {
325 iconInfoDirty = true;
328 if (ImGui::TableNextColumn()) {
330 if (ImGui::Checkbox(
"##fade-out", &icon.fade)) {
331 iconInfoDirty = true;
336 auto image = [&](IconInfo::Icon& ic,
const char*
id) {
338 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size));
340 if (ImGui::MenuItem(
"Remove image")) {
342 iconInfoDirty =
true;
343 ImGui::CloseCurrentPopup();
347 ImGui::Button(
"Select ...");
349 if (ImGui::IsItemClicked()) {
351 "Select image for icon",
"PNG (*.png){.png}",
352 [
this, &ic](
const std::string& filename) {
353 ic.filename = filename;
354 iconInfoDirty =
true;
358 if (ImGui::TableNextColumn()) {
359 image(icon.on,
"##on");
361 if (ImGui::TableNextColumn()) {
362 image(icon.off,
"##off");
364 if (ImGui::TableNextColumn()) {
365 ImGui::SetNextItemWidth(-FLT_MIN);
368 auto expr = std::string(icon.expr.getString());
369 if (ImGui::InputText(
"##expr", &expr)) {
371 iconInfoDirty =
true;
376 if (
int row = cmd.first; row != -1) {
377 switch (cmd.second) {
380 std::rotate(&iconInfo[0], &iconInfo[row], &iconInfo[row + 1]);
384 std::swap(iconInfo[row], iconInfo[row - 1]);
387 assert(row < narrow<int>(iconInfo.size() - 1));
388 std::swap(iconInfo[row], iconInfo[row + 1]);
391 assert(row < narrow<int>(iconInfo.size() - 1));
392 std::rotate(&iconInfo[row], &iconInfo[row + 1], &iconInfo[lastRow + 1]);
395 iconInfo.emplace(iconInfo.begin() + row, TclObject(
"true"),
"",
"",
true);
398 iconInfo.erase(iconInfo.begin() + row);
401 iconInfoDirty =
true;
405 if (ImGui::Button(
"Restore default")) {
bool loadLine(std::string_view name, zstring_view value)
void save(ImGuiTextBuffer &buf)
Interpreter & getInterpreter()
std::unique_ptr< ImGuiOpenFile > openFile
void save(ImGuiTextBuffer &buf) override
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
ImGuiOsdIcons(ImGuiManager &manager_)
void loadStart() override
bool validExpression(std::string_view expression)
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
bool stringToBool(string_view str)
std::pair< string_view, string_view > splitOnFirst(string_view str, string_view chars)
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
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 PopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
void StyleColor(bool active, Args &&...args)
void Disabled(bool b, std::invocable<> auto next)
void Indent(float indent_w, std::invocable<> auto next)
void ID_for_range(std::integral auto count, std::invocable< int > auto next)
SDLSurfacePtr load(const std::string &filename, bool want32bpp)
Load the given PNG file in a SDL_Surface.
This file implemented 3 utility functions:
const FileContext & systemFileContext()
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
void HelpMarker(std::string_view desc)
ImU32 getColor(imColor col)