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()
108 for (
auto& icon : iconInfo) {
109 auto load = [&](IconInfo::Icon& i) {
111 if (!i.filename.empty()) {
112 auto r = context.resolve(i.filename);
113 i.tex = loadTexture(context.resolve(i.filename), i.size);
126 auto m =
max(icon.on.size, icon.off.size);
128 iconsMaxSize =
max(iconsMaxSize, m);
131 iconInfoDirty =
false;
136 if (iconInfoDirty) loadIcons();
140 const auto& style = ImGui::GetStyle();
141 auto windowPadding = 2.0f *
gl::vec2(style.WindowPadding);
142 auto totalSize = windowPadding +
gl::vec2(iconsTotalSize) + float(iconsNumEnabled) *
gl::vec2(style.ItemSpacing);
143 auto minSize = iconsHorizontal
144 ?
gl::vec2(totalSize.x,
float(iconsMaxSize.y) + windowPadding.y)
145 :
gl::vec2(
float(iconsMaxSize.x) + windowPadding.x, totalSize.y);
146 if (!iconsHideTitle) {
147 minSize.y += 2.0f * style.FramePadding.y + ImGui::GetTextLineHeight();
149 auto maxSize = iconsHorizontal
152 ImGui::SetNextWindowSizeConstraints(minSize, maxSize);
155 const auto* mainViewPort = ImGui::GetMainViewport();
156 ImGui::SetNextWindowPos(
gl::vec2(mainViewPort->Pos) +
gl::vec2{10.0f, mainViewPort->WorkSize.y - 10.0f},
157 ImGuiCond_FirstUseEver,
159 int flags = iconsHideTitle ? ImGuiWindowFlags_NoTitleBar |
160 ImGuiWindowFlags_NoResize |
161 ImGuiWindowFlags_NoScrollbar |
162 ImGuiWindowFlags_NoScrollWithMouse |
163 ImGuiWindowFlags_NoCollapse |
164 ImGuiWindowFlags_NoBackground |
165 ImGuiWindowFlags_NoFocusOnAppearing |
166 ImGuiWindowFlags_NoNav |
167 (iconsAllowMove ? 0 : ImGuiWindowFlags_NoMove)
171 bool isOnMainViewPort = adjust.
post();
172 auto cursor0 = ImGui::GetCursorPos();
173 auto availableSize = ImGui::GetContentRegionAvail();
174 float slack = iconsHorizontal ? (availableSize.x - totalSize.x)
175 : (availableSize.y - totalSize.y);
176 float spacing = (iconsNumEnabled >= 2) ? (std::max(0.0f, slack) / float(iconsNumEnabled)) : 0.0f;
178 bool fade = iconsHideTitle && !ImGui::IsWindowDocked() && isOnMainViewPort;
179 for (
auto& icon : iconInfo) {
180 if (!icon.enable)
continue;
189 if (state != icon.lastState) {
190 icon.lastState = state;
193 const auto& io = ImGui::GetIO();
194 icon.time += io.DeltaTime;
196 if (!fade || !icon.fade)
return 1.0f;
197 auto t = icon.time - iconsFadeDelay;
198 if (
t <= 0.0f)
return 1.0f;
199 if (
t >= iconsFadeDuration)
return 0.0f;
200 return 1.0f - (
t / iconsFadeDuration);
203 const auto& ic = state ? icon.on : icon.off;
205 gl::vec2 cursor = ImGui::GetCursorPos();
206 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size),
207 {0.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 1.0f, 1.0f, alpha});
208 ImGui::SetCursorPos(cursor);
211 auto size =
gl::vec2(max(icon.on.size, icon.off.size));
212 (iconsHorizontal ? size.x : size.y) += spacing;
214 if (iconsHorizontal) ImGui::SameLine();
217 ImGui::SetCursorPos(cursor0);
218 ImGui::Dummy(availableSize);
219 if (iconsAllowMove) {
221 if (ImGui::MenuItem(
"Configure icons ...")) {
227 if (iconsHideTitle && ImGui::IsWindowFocused()) {
228 ImGui::SetWindowFocus(
nullptr);
233void ImGuiOsdIcons::paintConfigureIcons()
235 ImGui::SetNextWindowSize(
gl::vec2{37, 17} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
237 ImGui::Checkbox(
"Show OSD icons", &
showIcons);
240 ImGui::RadioButton(
"Horizontal", &iconsHorizontal, 1);
242 ImGui::RadioButton(
"Vertical", &iconsHorizontal, 0);
245 if (ImGui::Checkbox(
"Hide Title", &iconsHideTitle)) {
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", &iconsAllowMove);
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", &iconsFadeDelay, 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", &iconsFadeDuration, 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(2.0f * style.FramePadding.y + textHeight,
300 std::max(float(icon.on.size.y), float(icon.off.size.y)));
301 ImGui::Selectable(
"##row", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight));
302 if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
303 ImGui::OpenPopup(
"config-icon-context");
305 im::Popup(
"config-icon-context", [&]{
308 if (ImGui::MenuItem(
"Move to front")) cmd = {row, MOVE_FRONT};
309 if (ImGui::MenuItem(
"Move forwards")) cmd = {row, MOVE_FWD};
311 if (row != lastRow) {
312 if (ImGui::MenuItem(
"Move backwards"))cmd = {row, MOVE_BWD};
313 if (ImGui::MenuItem(
"Move to back")) cmd = {row, MOVE_BACK};
317 if (ImGui::MenuItem(
"Insert new row")) cmd = {row, INSERT};
318 if (ImGui::MenuItem(
"Delete current row")) cmd = {row, DELETE};
321 ImGui::SetCursorPos(pos);
322 if (ImGui::Checkbox(
"##enabled", &icon.enable)) {
323 iconInfoDirty = true;
326 if (ImGui::TableNextColumn()) {
328 if (ImGui::Checkbox(
"##fade-out", &icon.fade)) {
329 iconInfoDirty = true;
334 auto image = [&](IconInfo::Icon& ic,
const char*
id) {
336 ImGui::Image(ic.tex.getImGui(),
gl::vec2(ic.size));
338 if (ImGui::MenuItem(
"Remove image")) {
340 iconInfoDirty =
true;
341 ImGui::CloseCurrentPopup();
345 ImGui::Button(
"Select ...");
347 if (ImGui::IsItemClicked()) {
349 "Select image for icon",
"PNG (*.png){.png}",
350 [
this, &ic](
const std::string& filename) {
351 ic.filename = filename;
352 iconInfoDirty =
true;
356 if (ImGui::TableNextColumn()) {
357 image(icon.on,
"##on");
359 if (ImGui::TableNextColumn()) {
360 image(icon.off,
"##off");
362 if (ImGui::TableNextColumn()) {
363 ImGui::SetNextItemWidth(-FLT_MIN);
366 auto expr = std::string(icon.expr.getString());
367 if (ImGui::InputText(
"##expr", &expr)) {
369 iconInfoDirty =
true;
374 if (
int row = cmd.first; row != -1) {
375 switch (cmd.second) {
378 std::rotate(&iconInfo[0], &iconInfo[row], &iconInfo[row + 1]);
382 std::swap(iconInfo[row], iconInfo[row - 1]);
385 assert(row < narrow<int>(iconInfo.size() - 1));
386 std::swap(iconInfo[row], iconInfo[row + 1]);
389 assert(row < narrow<int>(iconInfo.size() - 1));
390 std::rotate(&iconInfo[row], &iconInfo[row + 1], &iconInfo[lastRow + 1]);
393 iconInfo.emplace(iconInfo.begin() + row, TclObject(
"true"),
"",
"",
true);
396 iconInfo.erase(iconInfo.begin() + row);
399 iconInfoDirty =
true;
403 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....
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)