mqSettingsGui.cpp (6168B)
1 #include "mqSettingsGui.h" 2 3 #include <iostream> 4 #include <cpp-terminal/input.hpp> 5 6 #include "audioOutputPA.h" 7 #include "midiDevice.h" 8 #include "portmidi/pm_common/portmidi.h" 9 #include "portaudio/include/portaudio.h" 10 11 #include "dsp56kEmu/logging.h" 12 13 using Key = Term::Key; 14 15 constexpr auto g_itemActive = Term::fg::bright_yellow; 16 constexpr auto g_itemDefault = Term::fg::white; 17 constexpr auto g_itemHovered = Term::bg::gray; 18 constexpr auto g_itemNotHovered = Term::bg::black; 19 20 constexpr int32_t g_maxEntries = 17; 21 22 namespace mqConsoleLib 23 { 24 SettingsGui::SettingsGui() 25 : m_win(140, 21) 26 { 27 } 28 29 void SettingsGui::render(int _midiInput, int _midiOutput, int _audioOutput) 30 { 31 if(!handleTerminalSize()) 32 m_win.clear(); 33 34 m_midiInput.clear(); 35 m_midiOutput.clear(); 36 m_audioOutput.clear(); 37 38 int x = 1; 39 int y = 1; 40 41 m_win.fill_bg(1,1, static_cast<int>(m_win.get_w()), 1, Term::bg::gray); 42 m_win.fill_fg(1,1, static_cast<int>(m_win.get_w()), 1, Term::fg::black); 43 44 x = renderSettings(x, y, _midiInput, 0, "MIDI Input"); x += 3; 45 x = renderSettings(x, y, _midiOutput, 1, "MIDI Output"); x += 3; 46 renderSettings(x, y, _audioOutput, 2, "Audio Output"); 47 48 m_win.fill_fg(1,21, 90, 21, Term::fg::gray); 49 m_win.print_str(1, 21, "Use Cursor Keys to navigate, Return to select a device. Escape to go back."); 50 51 std::cout << m_win.render(1,1,true) << std::flush; 52 } 53 54 void SettingsGui::onOpen() 55 { 56 GuiBase::onOpen(); 57 findSettings(); 58 } 59 60 void SettingsGui::onEnter() 61 { 62 m_enter = true; 63 } 64 65 void SettingsGui::onDown() 66 { 67 moveCursor(0,1); 68 } 69 70 void SettingsGui::onLeft() 71 { 72 moveCursor(-1,0); 73 } 74 75 void SettingsGui::onRight() 76 { 77 moveCursor(1,0); 78 } 79 80 void SettingsGui::onUp() 81 { 82 moveCursor(0,-1); 83 } 84 85 bool SettingsGui::processKey(int _ch) 86 { 87 switch (_ch) 88 { 89 case Key::ESC: 90 case Key::ENTER: 91 onEnter(); 92 return true; 93 case Key::ARROW_DOWN: 94 onDown(); 95 return true; 96 case Key::ARROW_LEFT: 97 onLeft(); 98 return true; 99 case Key::ARROW_RIGHT: 100 onRight(); 101 return true; 102 case Key::ARROW_UP: 103 onUp(); 104 return true; 105 default: 106 return false; 107 } 108 } 109 110 void SettingsGui::findSettings() 111 { 112 for (auto& setting : m_settings) 113 setting.clear(); 114 115 // MIDI 116 const auto devCount = Pm_CountDevices(); 117 118 for(int i=0; i<devCount; ++i) 119 { 120 const auto* di = Pm_GetDeviceInfo(i); 121 if(!di) 122 continue; 123 124 const auto name = MidiDevice::getDeviceNameFromId(i); 125 if(di->input) 126 m_settings[0].push_back({i, name}); 127 if(di->output) 128 m_settings[1].push_back({i, name}); 129 } 130 131 const auto count = Pa_GetDeviceCount(); 132 133 for(int i=0; i<count; ++i) 134 { 135 const auto* di = Pa_GetDeviceInfo(i); 136 if(!di) 137 continue; 138 139 if(di->maxOutputChannels < 2) 140 continue; 141 142 PaStreamParameters p{}; 143 144 p.channelCount = 2; 145 p.sampleFormat = paFloat32; 146 p.device = i; 147 p.suggestedLatency = di->defaultLowOutputLatency; 148 149 const auto supportResult = Pa_IsFormatSupported(nullptr, &p, 44100.0); 150 if(supportResult != paFormatIsSupported) 151 { 152 std::stringstream ss; 153 154 ss << "Audio Device not supported: " << AudioOutputPA::getDeviceNameFromId(i) << ", result " << Pa_GetErrorText(supportResult); 155 156 const auto* hostError = Pa_GetLastHostErrorInfo(); 157 if(hostError) 158 ss << ", host error info: " << hostError->errorCode << ", msg: " << (hostError->errorText ? hostError->errorText : "null"); 159 160 const auto msg(ss.str()); 161 LOG( msg); 162 continue; 163 } 164 165 const std::string name = AudioOutputPA::getDeviceNameFromId(i); 166 167 m_settings[2].push_back({i, name}); 168 } 169 } 170 171 void SettingsGui::changeDevice(const Setting& _value, int column) 172 { 173 switch (column) 174 { 175 case 0: 176 m_midiInput = _value.name; 177 break; 178 case 1: 179 m_midiOutput = _value.name; 180 break; 181 case 2: 182 m_audioOutput = _value.name; 183 break; 184 default: 185 break; 186 } 187 } 188 189 int SettingsGui::renderSettings(int x, int y, int _selectedId, const int _column, const std::string& _headline) 190 { 191 m_win.print_str(x, y, _headline); 192 y += 2; 193 194 int maxX = x + static_cast<int>(_headline.size()); 195 196 const auto& settings = m_settings[_column]; 197 198 const bool scroll = settings.size() > g_maxEntries; 199 200 auto& scrollPos = m_scrollPos[_column]; 201 202 if(scroll) 203 { 204 int focusPos = 0; 205 206 if(m_cursorX == _column) 207 { 208 focusPos = m_cursorY; 209 } 210 else 211 { 212 for(size_t i=0; i<settings.size(); ++i) 213 { 214 if(_selectedId == settings[i].devId) 215 { 216 focusPos = static_cast<int>(i); 217 break; 218 } 219 } 220 } 221 222 while((focusPos - scrollPos) < 3 && scrollPos > 0) 223 --scrollPos; 224 while((focusPos - scrollPos) > g_maxEntries - 3 && (scrollPos + g_maxEntries) < static_cast<int>(settings.size())) 225 ++scrollPos; 226 227 if(scrollPos > 0) 228 { 229 constexpr char32_t arrowUp = 0x2191; 230 m_win.set_char(x, y-1, arrowUp); 231 m_win.set_char(x + 3, y-1, arrowUp); 232 m_win.set_char(x + 6, y-1, arrowUp); 233 } 234 235 if((scrollPos + g_maxEntries) < static_cast<int>(settings.size())) 236 { 237 constexpr char32_t arrowDown = 0x2193; 238 m_win.set_char(x, y + g_maxEntries, arrowDown); 239 m_win.set_char(x + 3, y + g_maxEntries, arrowDown); 240 m_win.set_char(x + 6, y + g_maxEntries, arrowDown); 241 } 242 } 243 else 244 { 245 scrollPos = 0; 246 } 247 for(size_t i=0; i<std::min(static_cast<int>(settings.size()), g_maxEntries); ++i) 248 { 249 const int idx = static_cast<int>(i) + m_scrollPos[_column]; 250 251 if(idx < 0) 252 continue; 253 254 if(idx >= settings.size()) 255 break; 256 257 const auto& s = settings[idx]; 258 259 const auto len = static_cast<int>(s.name.size()); 260 261 if(x + len > maxX) 262 maxX = x + len; 263 264 if(m_cursorY == idx && m_cursorX == _column) 265 { 266 m_win.fill_bg(x,y, x + len - 1, y, g_itemHovered); 267 if(m_enter) 268 { 269 m_enter = false; 270 changeDevice(s, _column); 271 } 272 } 273 else 274 { 275 m_win.fill_bg(x,y, x + len - 1, y, g_itemNotHovered); 276 } 277 m_win.fill_fg(x,y, x + len, y, s.devId == _selectedId ? g_itemActive : g_itemDefault); 278 m_win.print_str(x, y, s.name); 279 ++y; 280 } 281 return maxX; 282 } 283 284 void SettingsGui::moveCursor(int x, int y) 285 { 286 m_cursorX += x; 287 m_cursorY += y; 288 289 if(m_cursorX >= static_cast<int>(m_settings.size())) 290 m_cursorX = 0; 291 if(m_cursorX < 0) 292 m_cursorX = static_cast<int>(m_settings.size() - 1); 293 if(m_cursorY < 0) 294 m_cursorY = m_settings[m_cursorX].empty() ? 0 : static_cast<int>(m_settings[m_cursorX].size()) - 1; 295 if(m_cursorY >= static_cast<int>(m_settings[m_cursorX].size())) 296 m_cursorY = 0; 297 } 298 }