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;
201 gl::vec2 cursor = ImGui::GetCursorPos();
202 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size),
203 {0.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, alpha});
204 ImGui::SetCursorPos(cursor);
207 auto size =
gl::vec2(max(icon.on.size, icon.off.size));
208 (iconsHorizontal ? size.x : size.y) += spacing;
210 if (iconsHorizontal) ImGui::SameLine();
213 ImGui::SetCursorPos(cursor0);
214 ImGui::Dummy(availableSize);
215 if (iconsAllowMove) {
217 if (ImGui::MenuItem(
"Configure icons ...")) {
225void ImGuiOsdIcons::paintConfigureIcons()
227 ImGui::SetNextWindowSize(
gl::vec2{37, 17} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
229 ImGui::Checkbox(
"Show OSD icons", &
showIcons);
232 ImGui::RadioButton(
"Horizontal", &iconsHorizontal, 1);
234 ImGui::RadioButton(
"Vertical", &iconsHorizontal, 0);
237 if (ImGui::Checkbox(
"Hide Title", &iconsHideTitle)) {
239 for (
auto& icon : iconInfo) {
243 HelpMarker(
"When you want the icons inside the MSX window, you might want to hide the window title.\n"
244 "To further hide the icons, it's possible to make them fade-out after some time.");
247 ImGui::Checkbox(
"Allow move", &iconsAllowMove);
248 HelpMarker(
"When the icons are in the MSX window (without title bar), you might want "
249 "to lock them in place, to prevent them from being moved by accident.\n"
250 "Move by click and drag the icon box.\n");
251 auto width = ImGui::GetFontSize() * 10.0f;
252 ImGui::SetNextItemWidth(width);
253 ImGui::SliderFloat(
"Fade-out delay", &iconsFadeDelay, 0.0f, 30.0f,
"%.1f");
254 HelpMarker(
"After some delay, fade-out icons that haven't changed status for a while.\n"
255 "Note: by default some icons are configured to never fade-out.");
256 ImGui::SetNextItemWidth(width);
257 ImGui::SliderFloat(
"Fade-out duration", &iconsFadeDuration, 0.0f, 30.0f,
"%.1f");
264 HelpMarker(
"Change the order and properties of the icons (for advanced users).\n"
265 "Right-click in a row to reorder, insert, delete that row.\n"
266 "Right-click on an icon to remove it.");
267 int flags = ImGuiTableFlags_RowBg |
268 ImGuiTableFlags_BordersOuterH |
269 ImGuiTableFlags_BordersInnerH |
270 ImGuiTableFlags_BordersV |
271 ImGuiTableFlags_BordersOuter |
272 ImGuiTableFlags_Resizable;
274 ImGui::TableSetupColumn(
"Enabled", ImGuiTableColumnFlags_WidthFixed);
275 ImGui::TableSetupColumn(
"Fade-out", ImGuiTableColumnFlags_WidthFixed);
276 ImGui::TableSetupColumn(
"True-image", ImGuiTableColumnFlags_WidthFixed);
277 ImGui::TableSetupColumn(
"False-image", ImGuiTableColumnFlags_WidthFixed);
278 ImGui::TableSetupColumn(
"Expression");
279 ImGui::TableHeadersRow();
281 enum class Cmd { MOVE_FRONT, MOVE_FWD, MOVE_BWD, MOVE_BACK, INSERT, DELETE };
283 std::pair<int, Cmd> cmd(-1, MOVE_FRONT);
284 auto lastRow = narrow<int>(iconInfo.size()) - 1;
286 auto& icon = iconInfo[row];
287 if (ImGui::TableNextColumn()) {
288 auto pos = ImGui::GetCursorPos();
289 const auto& style = ImGui::GetStyle();
290 auto textHeight = ImGui::GetTextLineHeight();
291 float rowHeight = std::max(2.0f * style.FramePadding.y + textHeight,
292 std::max(float(icon.on.size.y), float(icon.off.size.y)));
293 ImGui::Selectable(
"##row", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight));
294 if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
295 ImGui::OpenPopup(
"config-icon-context");
297 im::Popup(
"config-icon-context", [&]{
300 if (ImGui::MenuItem(
"Move to front")) cmd = {row, MOVE_FRONT};
301 if (ImGui::MenuItem(
"Move forwards")) cmd = {row, MOVE_FWD};
303 if (row != lastRow) {
304 if (ImGui::MenuItem(
"Move backwards"))cmd = {row, MOVE_BWD};
305 if (ImGui::MenuItem(
"Move to back")) cmd = {row, MOVE_BACK};
309 if (ImGui::MenuItem(
"Insert new row")) cmd = {row, INSERT};
310 if (ImGui::MenuItem(
"Delete current row")) cmd = {row, DELETE};
313 ImGui::SetCursorPos(pos);
314 if (ImGui::Checkbox(
"##enabled", &icon.enable)) {
315 iconInfoDirty = true;
318 if (ImGui::TableNextColumn()) {
320 if (ImGui::Checkbox(
"##fade-out", &icon.fade)) {
321 iconInfoDirty = true;
326 auto image = [&](IconInfo::Icon& ic,
const char*
id) {
328 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size));
330 if (ImGui::MenuItem(
"Remove image")) {
332 iconInfoDirty =
true;
333 ImGui::CloseCurrentPopup();
337 ImGui::Button(
"Select ...");
339 if (ImGui::IsItemClicked()) {
341 "Select image for icon",
"PNG (*.png){.png}",
342 [
this, &ic](
const std::string& filename) {
343 ic.filename = filename;
344 iconInfoDirty =
true;
348 if (ImGui::TableNextColumn()) {
349 image(icon.on,
"##on");
351 if (ImGui::TableNextColumn()) {
352 image(icon.off,
"##off");
354 if (ImGui::TableNextColumn()) {
355 ImGui::SetNextItemWidth(-FLT_MIN);
358 auto expr = std::string(icon.expr.getString());
359 if (ImGui::InputText(
"##expr", &expr)) {
361 iconInfoDirty =
true;
366 if (
int row = cmd.first; row != -1) {
367 switch (cmd.second) {
370 std::rotate(&iconInfo[0], &iconInfo[row], &iconInfo[row + 1]);
374 std::swap(iconInfo[row], iconInfo[row - 1]);
377 assert(row < narrow<int>(iconInfo.size() - 1));
378 std::swap(iconInfo[row], iconInfo[row + 1]);
381 assert(row < narrow<int>(iconInfo.size() - 1));
382 std::rotate(&iconInfo[row], &iconInfo[row + 1], &iconInfo[lastRow + 1]);
385 iconInfo.emplace(iconInfo.begin() + row, TclObject(
"true"),
"",
"",
true);
388 iconInfo.erase(iconInfo.begin() + row);
391 iconInfoDirty =
true;
395 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(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)