xemu/ui/xui/monitor.cc
Matt Borgerson 9c06980275 ui: Redesign user interface
Introduces a new user interface that looks much nicer, is easier to
navigate with controllers, provides more context to users, and is
scalable. Some additional features are included.

* Adds 'popup menu' with actions that can be used easily from controller
* Adds 'main menu', unifying other configuration dialogs
* Adds port-forwarding user interface
* Adds screenshot feature
* Adds volume control feature
* Adds gamepad auto-bind option
* Adds vsync configuration option
* Adds auto UI scaling
* Adds preferred window size selection
* Adds AV pack selection
* Exposes some existing config items in GUI
2022-05-07 16:09:34 -07:00

156 lines
5.8 KiB
C++

//
// xemu User Interface
//
// Copyright (C) 2020-2022 Matt Borgerson
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "monitor.hh"
#include "imgui.h"
#include "misc.hh"
#include "font-manager.hh"
// Portable helpers
static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; }
static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)str, len); }
static void Strtrim(char* str) { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; }
MonitorWindow::MonitorWindow()
{
is_open = false;
memset(InputBuf, 0, sizeof(InputBuf));
HistoryPos = -1;
AutoScroll = true;
ScrollToBottom = false;
}
MonitorWindow::~MonitorWindow()
{
}
void MonitorWindow::Draw()
{
if (!is_open) return;
int style_pop_cnt = PushWindowTransparencySettings(true) + 1;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImU32(ImColor(0, 0, 0, 80)));
ImGuiIO& io = ImGui::GetIO();
ImVec2 window_pos = ImVec2(0,io.DisplaySize.y/2);
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Appearing);
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y/2), ImGuiCond_Appearing);
if (ImGui::Begin("Monitor", &is_open, ImGuiWindowFlags_NoCollapse)) {
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
ImGui::TextUnformatted(xemu_get_monitor_buffer());
ImGui::PopFont();
if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
ImGui::SetScrollHereY(1.0f);
}
ScrollToBottom = false;
ImGui::PopStyleVar();
ImGui::EndChild();
ImGui::Separator();
// Command-line
bool reclaim_focus = ImGui::IsWindowAppearing();
ImGui::SetNextItemWidth(-1);
ImGui::PushFont(g_font_mgr.m_fixed_width_font);
if (ImGui::InputText("#commandline", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) {
char* s = InputBuf;
Strtrim(s);
if (s[0])
ExecCommand(s);
strcpy(s, "");
reclaim_focus = true;
}
ImGui::PopFont();
// Auto-focus on window apparition
ImGui::SetItemDefaultFocus();
if (reclaim_focus) {
ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
}
}
ImGui::End();
ImGui::PopStyleColor(style_pop_cnt);
}
void MonitorWindow::ToggleOpen(void)
{
is_open = !is_open;
}
void MonitorWindow::ExecCommand(const char* command_line)
{
xemu_run_monitor_command(command_line);
// Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
HistoryPos = -1;
for (int i = History.Size-1; i >= 0; i--)
if (Stricmp(History[i], command_line) == 0)
{
free(History[i]);
History.erase(History.begin() + i);
break;
}
History.push_back(Strdup(command_line));
// On commad input, we scroll to bottom even if AutoScroll==false
ScrollToBottom = true;
}
int MonitorWindow::TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
{
MonitorWindow* console = (MonitorWindow*)data->UserData;
return console->TextEditCallback(data);
}
int MonitorWindow::TextEditCallback(ImGuiInputTextCallbackData* data)
{
switch (data->EventFlag)
{
case ImGuiInputTextFlags_CallbackHistory:
{
// Example of HISTORY
const int prev_history_pos = HistoryPos;
if (data->EventKey == ImGuiKey_UpArrow)
{
if (HistoryPos == -1)
HistoryPos = History.Size - 1;
else if (HistoryPos > 0)
HistoryPos--;
}
else if (data->EventKey == ImGuiKey_DownArrow)
{
if (HistoryPos != -1)
if (++HistoryPos >= History.Size)
HistoryPos = -1;
}
// A better implementation would preserve the data on the current input line along with cursor position.
if (prev_history_pos != HistoryPos)
{
const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, history_str);
}
}
}
return 0;
}
MonitorWindow monitor_window;