16#include <imgui_stdlib.h>
20using namespace std::literals;
36 for (
const auto& [i, icon] :
enumerate(iconInfo)) {
37 auto n = narrow<int>(i + 1);
38 buf.appendf(
"icon.%d.enabled=%d\n", n, icon.enable);
39 buf.appendf(
"icon.%d.fade=%d\n", n, icon.fade);
40 buf.appendf(
"icon.%d.on-image=%s\n", n, icon.on.filename.c_str());
41 buf.appendf(
"icon.%d.off-image=%s\n", n, icon.off.filename.c_str());
42 buf.appendf(
"icon.%d.expr=%s\n", n, icon.expr.getString().c_str());
55 }
else if (name.starts_with(
"icon.")) {
57 auto n = StringOp::stringTo<size_t>(numStr);
58 if (!n || *n == 0)
return;
59 while (iconInfo.size() < *n) {
60 iconInfo.emplace_back();
62 auto& info = iconInfo[*n - 1];
63 if (suffix ==
"enabled") {
65 }
else if (suffix ==
"fade") {
67 }
else if (suffix ==
"on-image") {
68 info.on.filename = value;
69 }
else if (suffix ==
"off-image") {
70 info.off.filename = value;
71 }
else if (suffix ==
"expr") {
79 if (iconInfo.empty()) setDefaultIcons();
83void ImGuiOsdIcons::setDefaultIcons()
86 iconInfo.emplace_back(
TclObject(
"$led_power"),
"skins/set1/power-on.png",
"skins/set1/power-off.png",
true);
87 iconInfo.emplace_back(
TclObject(
"$led_caps" ),
"skins/set1/caps-on.png",
"skins/set1/caps-off.png",
true);
88 iconInfo.emplace_back(
TclObject(
"$led_kana" ),
"skins/set1/kana-on.png",
"skins/set1/kana-off.png",
true);
89 iconInfo.emplace_back(
TclObject(
"$led_pause"),
"skins/set1/pause-on.png",
"skins/set1/pause-off.png",
true);
90 iconInfo.emplace_back(
TclObject(
"$led_turbo"),
"skins/set1/turbo-on.png",
"skins/set1/turbo-off.png",
true);
91 iconInfo.emplace_back(
TclObject(
"$led_FDD" ),
"skins/set1/fdd-on.png",
"skins/set1/fdd-off.png",
true);
92 iconInfo.emplace_back(
TclObject(
"$pause" ),
"skins/set1/pause.png",
"",
false);
93 iconInfo.emplace_back(
TclObject(
"!$throttle || $fastforward"),
"skins/set1/throttle.png",
"",
false);
94 iconInfo.emplace_back(
TclObject(
"$mute" ),
"skins/set1/mute.png",
"",
false);
95 iconInfo.emplace_back(
TclObject(
"$breaked" ),
"skins/set1/breaked.png",
"",
false);
99void ImGuiOsdIcons::loadIcons()
105 for (
auto& icon : iconInfo) {
106 auto load = [&](IconInfo::Icon& i) {
108 if (!i.filename.empty()) {
109 auto r = context.resolve(i.filename);
110 i.tex = loadTexture(context.resolve(i.filename), i.size);
123 auto m =
max(icon.on.size, icon.off.size);
125 iconsMaxSize =
max(iconsMaxSize, m);
128 iconInfoDirty =
false;
133 if (iconInfoDirty) loadIcons();
137 const auto& style = ImGui::GetStyle();
138 auto windowPadding = 2.0f *
gl::vec2(style.WindowPadding);
139 auto totalSize = windowPadding +
gl::vec2(iconsTotalSize) + float(iconsNumEnabled) *
gl::vec2(style.ItemSpacing);
140 auto minSize = iconsHorizontal
141 ?
gl::vec2(totalSize.x,
float(iconsMaxSize.y) + windowPadding.y)
142 :
gl::vec2(
float(iconsMaxSize.x) + windowPadding.x, totalSize.y);
143 if (!iconsHideTitle) {
144 minSize.y += 2.0f * style.FramePadding.y + ImGui::GetTextLineHeight();
146 auto maxSize = iconsHorizontal
149 ImGui::SetNextWindowSizeConstraints(minSize, maxSize);
152 const auto* mainViewPort = ImGui::GetMainViewport();
153 ImGui::SetNextWindowPos(
gl::vec2(mainViewPort->Pos) +
gl::vec2{10.0f, mainViewPort->WorkSize.y - 10.0f},
154 ImGuiCond_FirstUseEver,
156 int flags = iconsHideTitle ? ImGuiWindowFlags_NoTitleBar |
157 ImGuiWindowFlags_NoResize |
158 ImGuiWindowFlags_NoScrollbar |
159 ImGuiWindowFlags_NoScrollWithMouse |
160 ImGuiWindowFlags_NoCollapse |
161 ImGuiWindowFlags_NoBackground |
162 ImGuiWindowFlags_NoFocusOnAppearing |
163 (iconsAllowMove ? 0 : ImGuiWindowFlags_NoMove)
167 bool isOnMainViewPort = adjust.
post();
168 auto cursor0 = ImGui::GetCursorPos();
169 auto availableSize = ImGui::GetContentRegionAvail();
170 float slack = iconsHorizontal ? (availableSize.x - totalSize.x)
171 : (availableSize.y - totalSize.y);
172 float spacing = (iconsNumEnabled >= 2) ? (std::max(0.0f, slack) / float(iconsNumEnabled)) : 0.0f;
174 bool fade = iconsHideTitle && !ImGui::IsWindowDocked() && isOnMainViewPort;
175 for (
auto& icon : iconInfo) {
176 if (!icon.enable)
continue;
185 if (state != icon.lastState) {
186 icon.lastState = state;
189 const auto& io = ImGui::GetIO();
190 icon.time += io.DeltaTime;
192 if (!fade || !icon.fade)
return 1.0f;
193 auto t = icon.time - iconsFadeDelay;
194 if (
t <= 0.0f)
return 1.0f;
195 if (
t >= iconsFadeDuration)
return 0.0f;
196 return 1.0f - (
t / iconsFadeDuration);
199 const auto& ic = state ? icon.on : icon.off;
200 gl::vec2 cursor = ImGui::GetCursorPos();
201 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size),
202 {0.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, alpha});
204 ImGui::SetCursorPos(cursor);
205 auto size =
gl::vec2(max(icon.on.size, icon.off.size));
206 (iconsHorizontal ? size.x : size.y) += spacing;
208 if (iconsHorizontal) ImGui::SameLine();
211 ImGui::SetCursorPos(cursor0);
212 ImGui::Dummy(availableSize);
213 if (iconsAllowMove) {
215 if (ImGui::MenuItem(
"Configure icons ...")) {
223void ImGuiOsdIcons::paintConfigureIcons()
225 ImGui::SetNextWindowSize(
gl::vec2{37, 17} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
227 ImGui::Checkbox(
"Show OSD icons", &
showIcons);
230 ImGui::RadioButton(
"Horizontal", &iconsHorizontal, 1);
232 ImGui::RadioButton(
"Vertical", &iconsHorizontal, 0);
235 if (ImGui::Checkbox(
"Hide Title", &iconsHideTitle)) {
237 for (
auto& icon : iconInfo) {
241 HelpMarker(
"When you want the icons inside the MSX window, you might want to hide the window title.\n"
242 "To further hide the icons, it's possible to make them fade-out after some time.");
245 ImGui::Checkbox(
"Allow move", &iconsAllowMove);
246 HelpMarker(
"When the icons are in the MSX window (without title bar), you might want "
247 "to lock them in place, to prevent them from being moved by accident.\n"
248 "Move by click and drag the icon box.\n");
249 auto width = ImGui::GetFontSize() * 10.0f;
250 ImGui::SetNextItemWidth(width);
251 ImGui::SliderFloat(
"Fade-out delay", &iconsFadeDelay, 0.0f, 30.0f,
"%.1f");
252 HelpMarker(
"After some delay, fade-out icons that haven't changed status for a while.\n"
253 "Note: by default some icons are configured to never fade-out.");
254 ImGui::SetNextItemWidth(width);
255 ImGui::SliderFloat(
"Fade-out duration", &iconsFadeDuration, 0.0f, 30.0f,
"%.1f");
262 HelpMarker(
"Change the order and properties of the icons (for advanced users).\n"
263 "Right-click in a row to reorder, insert, delete that row.\n"
264 "Right-click on an icon to remove it.");
265 int flags = ImGuiTableFlags_RowBg |
266 ImGuiTableFlags_BordersOuterH |
267 ImGuiTableFlags_BordersInnerH |
268 ImGuiTableFlags_BordersV |
269 ImGuiTableFlags_BordersOuter |
270 ImGuiTableFlags_Resizable;
272 ImGui::TableSetupColumn(
"Enabled", ImGuiTableColumnFlags_WidthFixed);
273 ImGui::TableSetupColumn(
"Fade-out", ImGuiTableColumnFlags_WidthFixed);
274 ImGui::TableSetupColumn(
"True-image", ImGuiTableColumnFlags_WidthFixed);
275 ImGui::TableSetupColumn(
"False-image", ImGuiTableColumnFlags_WidthFixed);
276 ImGui::TableSetupColumn(
"Expression");
277 ImGui::TableHeadersRow();
279 enum class Cmd { MOVE_FRONT, MOVE_FWD, MOVE_BWD, MOVE_BACK, INSERT, DELETE };
281 std::pair<int, Cmd> cmd(-1, MOVE_FRONT);
282 auto lastRow = narrow<int>(iconInfo.size()) - 1;
284 auto& icon = iconInfo[row];
285 if (ImGui::TableNextColumn()) {
286 auto pos = ImGui::GetCursorPos();
287 const auto& style = ImGui::GetStyle();
288 auto textHeight = ImGui::GetTextLineHeight();
289 float rowHeight = std::max(2.0f * style.FramePadding.y + textHeight,
290 std::max(float(icon.on.size.y), float(icon.off.size.y)));
291 ImGui::Selectable(
"##row", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight));
292 if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
293 ImGui::OpenPopup(
"config-icon-context");
295 im::Popup(
"config-icon-context", [&]{
298 if (ImGui::MenuItem(
"Move to front")) cmd = {row, MOVE_FRONT};
299 if (ImGui::MenuItem(
"Move forwards")) cmd = {row, MOVE_FWD};
301 if (row != lastRow) {
302 if (ImGui::MenuItem(
"Move backwards"))cmd = {row, MOVE_BWD};
303 if (ImGui::MenuItem(
"Move to back")) cmd = {row, MOVE_BACK};
307 if (ImGui::MenuItem(
"Insert new row")) cmd = {row, INSERT};
308 if (ImGui::MenuItem(
"Delete current row")) cmd = {row, DELETE};
311 ImGui::SetCursorPos(pos);
312 if (ImGui::Checkbox(
"##enabled", &icon.enable)) {
313 iconInfoDirty = true;
316 if (ImGui::TableNextColumn()) {
318 if (ImGui::Checkbox(
"##fade-out", &icon.fade)) {
319 iconInfoDirty = true;
324 auto image = [&](IconInfo::Icon& ic,
const char*
id) {
326 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size));
328 if (ImGui::MenuItem(
"Remove image")) {
330 iconInfoDirty =
true;
331 ImGui::CloseCurrentPopup();
335 ImGui::Button(
"Select ...");
337 if (ImGui::IsItemClicked()) {
339 "Select image for icon",
"PNG (*.png){.png}",
340 [
this, &ic](
const std::string& filename) {
341 ic.filename = filename;
342 iconInfoDirty =
true;
346 if (ImGui::TableNextColumn()) {
347 image(icon.on,
"##on");
349 if (ImGui::TableNextColumn()) {
350 image(icon.off,
"##off");
352 if (ImGui::TableNextColumn()) {
353 ImGui::SetNextItemWidth(-FLT_MIN);
356 auto expr = std::string(icon.expr.getString());
357 if (ImGui::InputText(
"##expr", &expr)) {
359 iconInfoDirty =
true;
364 if (
int row = cmd.first; row != -1) {
365 switch (cmd.second) {
368 std::rotate(&iconInfo[0], &iconInfo[row], &iconInfo[row + 1]);
372 std::swap(iconInfo[row], iconInfo[row - 1]);
375 assert(row < narrow<int>(iconInfo.size() - 1));
376 std::swap(iconInfo[row], iconInfo[row + 1]);
379 assert(row < narrow<int>(iconInfo.size() - 1));
380 std::rotate(&iconInfo[row], &iconInfo[row + 1], &iconInfo[lastRow + 1]);
383 iconInfo.emplace(iconInfo.begin() + row, TclObject(
"true"),
"",
"",
true);
386 iconInfo.erase(iconInfo.begin() + row);
389 iconInfoDirty =
true;
393 if (ImGui::Button(
"Restore default")) {
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....
void TextUnformatted(const std::string &str)
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(int 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)