// Copyright 2010 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DolphinWX/Input/InputConfigDiag.h" #include "DolphinWX/WxUtils.h" #include "InputCommon/ControllerEmu/Control/Control.h" #include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h" #include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" #include "InputCommon/ControllerEmu/ControlGroup/Cursor.h" #include "InputCommon/ControllerEmu/ControlGroup/Force.h" #include "InputCommon/ControllerEmu/ControlGroup/Slider.h" #include "InputCommon/ControllerEmu/ControlGroup/Tilt.h" #include "InputCommon/ControllerEmu/ControlGroup/Triggers.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/Device.h" struct ShapePosition { double max; double diag; double box; double scale; double dz; double range; wxCoord offset; }; // regular octagon static void DrawOctagon(wxGraphicsContext* gc, ShapePosition p) { static constexpr int vertices = 8; double radius = p.max; wxGraphicsPath path = gc->CreatePath(); double angle = 2.0 * M_PI / vertices; for (int i = 0; i < vertices; i++) { double a = angle * i; double x = radius * cos(a); double y = radius * sin(a); if (i == 0) path.MoveToPoint(x, y); else path.AddLineToPoint(x, y); } path.CloseSubpath(); wxGraphicsMatrix matrix = gc->CreateMatrix(); matrix.Translate(p.offset, p.offset); path.Transform(matrix); gc->DrawPath(path); } // irregular dodecagon static void DrawDodecagon(wxGraphicsContext* gc, ShapePosition p) { wxGraphicsPath path = gc->CreatePath(); path.MoveToPoint(p.dz, p.max); path.AddLineToPoint(p.diag, p.diag); path.AddLineToPoint(p.max, p.dz); path.AddLineToPoint(p.max, -p.dz); path.AddLineToPoint(p.diag, -p.diag); path.AddLineToPoint(p.dz, -p.max); path.AddLineToPoint(-p.dz, -p.max); path.AddLineToPoint(-p.diag, -p.diag); path.AddLineToPoint(-p.max, -p.dz); path.AddLineToPoint(-p.max, p.dz); path.AddLineToPoint(-p.diag, p.diag); path.AddLineToPoint(-p.dz, p.max); path.CloseSubpath(); wxGraphicsMatrix matrix = gc->CreateMatrix(); matrix.Translate(p.offset, p.offset); path.Transform(matrix); gc->DrawPath(path); } static void DrawCenteredRectangle(wxGraphicsContext* gc, double x, double y, double w, double h) { x -= w / 2; y -= h / 2; gc->DrawRectangle(x, y, w, h); } #define VIS_BITMAP_SIZE 64 #define VIS_NORMALIZE(a) ((a / 2.0) + 0.5) #define VIS_COORD(a) ((VIS_NORMALIZE(a)) * VIS_BITMAP_SIZE) #define COORD_VIS_SIZE 4 static void DrawCoordinate(wxGraphicsContext* gc, ControlState x, ControlState y) { double xc = VIS_COORD(x); double yc = VIS_COORD(y); DrawCenteredRectangle(gc, xc, yc, COORD_VIS_SIZE, COORD_VIS_SIZE); } static void DrawButton(const std::vector& bitmasks, unsigned int buttons, unsigned int n, wxGraphicsContext* gc, ControlGroupBox* g, unsigned int row, const wxGraphicsMatrix& null_matrix) { if (buttons & bitmasks[(row * 8) + n]) { gc->SetBrush(*wxRED_BRUSH); } else { unsigned char amt = 255 - g->control_group->controls[(row * 8) + n]->control_ref->State() * 128; gc->SetBrush(wxBrush(wxColour(amt, amt, amt))); } gc->DrawRectangle(n * 12, (row == 0) ? 0 : (row * 11), 14, 12); // text const std::string name = g->control_group->controls[(row * 8) + n]->ui_name; // Matrix transformation needs to be disabled so we don't draw scaled/zoomed text. wxGraphicsMatrix old_matrix = gc->GetTransform(); gc->SetTransform(null_matrix); // bit of hax so ZL, ZR show up as L, R gc->DrawText(wxUniChar((name[1] && name[1] < 'a') ? name[1] : name[0]), (n * 12 + 2) * g->m_scale, (1 + (row == 0 ? 0 : row * 11)) * g->m_scale); gc->SetTransform(old_matrix); } static void DrawControlGroupBox(wxGraphicsContext* gc, ControlGroupBox* g) { wxGraphicsMatrix null_matrix = gc->GetTransform(); wxGraphicsMatrix scale_matrix = null_matrix; scale_matrix.Scale(g->m_scale, g->m_scale); gc->SetTransform(scale_matrix); switch (g->control_group->type) { case ControllerEmu::GroupType::Tilt: case ControllerEmu::GroupType::Stick: case ControllerEmu::GroupType::Cursor: { // this is starting to be a mess combining all these in one case ControlState x = 0, y = 0, z = 0; switch (g->control_group->type) { case ControllerEmu::GroupType::Stick: ((ControllerEmu::AnalogStick*)g->control_group)->GetState(&x, &y); break; case ControllerEmu::GroupType::Tilt: ((ControllerEmu::Tilt*)g->control_group)->GetState(&x, &y); break; case ControllerEmu::GroupType::Cursor: ((ControllerEmu::Cursor*)g->control_group)->GetState(&x, &y, &z); break; case ControllerEmu::GroupType::Other: case ControllerEmu::GroupType::MixedTriggers: case ControllerEmu::GroupType::Buttons: case ControllerEmu::GroupType::Force: case ControllerEmu::GroupType::Extension: case ControllerEmu::GroupType::Triggers: case ControllerEmu::GroupType::Slider: break; } // ir cursor forward movement if (g->control_group->type == ControllerEmu::GroupType::Cursor) { gc->SetBrush(z ? *wxRED_BRUSH : *wxGREY_BRUSH); wxGraphicsPath path = gc->CreatePath(); path.AddRectangle(0, 31 - z * 31, 64, 2); gc->FillPath(path); } // input zone gc->SetPen(*wxLIGHT_GREY_PEN); if (g->control_group->type == ControllerEmu::GroupType::Stick) { gc->SetBrush(wxColour(0xDDDDDD)); // Light Gray ShapePosition p; p.box = 64; p.offset = p.box / 2; p.range = 256; p.scale = p.box / p.range; p.dz = 15 * p.scale; bool octagon = false; if (g->control_group->name == "Main Stick") { p.max = 87 * p.scale; p.diag = 55 * p.scale; } else if (g->control_group->name == "C-Stick") { p.max = 74 * p.scale; p.diag = 46 * p.scale; } else { p.scale = 1; p.max = 32; octagon = true; } if (octagon) DrawOctagon(gc, p); else DrawDodecagon(gc, p); } else { gc->SetBrush(*wxWHITE_BRUSH); gc->DrawRectangle(16, 16, 32, 32); } if (g->control_group->type != ControllerEmu::GroupType::Cursor) { const int deadzone_idx = g->control_group->type == ControllerEmu::GroupType::Stick ? ControllerEmu::AnalogStick::SETTING_DEADZONE : 0; wxGraphicsPath path = gc->CreatePath(); path.AddCircle(VIS_BITMAP_SIZE / 2, VIS_BITMAP_SIZE / 2, g->control_group->numeric_settings[deadzone_idx]->GetValue() * VIS_BITMAP_SIZE / 2); gc->SetBrush(*wxLIGHT_GREY_BRUSH); gc->FillPath(path); } // raw dot ControlState xx, yy; xx = g->control_group->controls[3]->control_ref->State(); xx -= g->control_group->controls[2]->control_ref->State(); yy = g->control_group->controls[1]->control_ref->State(); yy -= g->control_group->controls[0]->control_ref->State(); gc->SetPen(*wxTRANSPARENT_PEN); gc->SetBrush(*wxGREY_BRUSH); DrawCoordinate(gc, xx, yy); // adjusted dot if (x != 0 || y != 0) { gc->SetBrush(*wxRED_BRUSH); // XXX: The adjusted values flip the Y axis to be in the format // the Wii expects. Should this be in WiimoteEmu.cpp instead? DrawCoordinate(gc, x, -y); } } break; case ControllerEmu::GroupType::Force: { ControlState raw_dot[3]; ControlState adj_dot[3]; const ControlState deadzone = g->control_group->numeric_settings[0]->GetValue(); // adjusted ((ControllerEmu::Force*)g->control_group)->GetState(adj_dot); // raw for (unsigned int i = 0; i < 3; ++i) { raw_dot[i] = (g->control_group->controls[i * 2 + 1]->control_ref->State() - g->control_group->controls[i * 2]->control_ref->State()); } // deadzone rect for forward/backward visual gc->SetPen(*wxTRANSPARENT_PEN); gc->SetBrush(*wxLIGHT_GREY_BRUSH); int deadzone_height = deadzone * VIS_BITMAP_SIZE; DrawCenteredRectangle(gc, 0, VIS_BITMAP_SIZE / 2, VIS_BITMAP_SIZE, deadzone_height); #define LINE_HEIGHT 2 int line_y; // raw forward/background line gc->SetBrush(*wxGREY_BRUSH); line_y = VIS_COORD(raw_dot[2]); DrawCenteredRectangle(gc, VIS_BITMAP_SIZE / 2, line_y, VIS_BITMAP_SIZE, LINE_HEIGHT); // adjusted forward/background line if (adj_dot[2] != 0.0) { gc->SetBrush(*wxRED_BRUSH); line_y = VIS_COORD(adj_dot[2]); DrawCenteredRectangle(gc, VIS_BITMAP_SIZE / 2, line_y, VIS_BITMAP_SIZE, LINE_HEIGHT); } #define DEADZONE_RECT_SIZE 32 // empty deadzone square gc->SetPen(*wxLIGHT_GREY_PEN); gc->SetBrush(*wxWHITE_BRUSH); DrawCenteredRectangle(gc, VIS_BITMAP_SIZE / 2, VIS_BITMAP_SIZE / 2, DEADZONE_RECT_SIZE, DEADZONE_RECT_SIZE); // deadzone square gc->SetPen(*wxTRANSPARENT_PEN); gc->SetBrush(*wxLIGHT_GREY_BRUSH); int dz_size = (deadzone * DEADZONE_RECT_SIZE); DrawCenteredRectangle(gc, VIS_BITMAP_SIZE / 2, VIS_BITMAP_SIZE / 2, dz_size, dz_size); // raw dot gc->SetBrush(*wxGREY_BRUSH); DrawCoordinate(gc, raw_dot[1], raw_dot[0]); // adjusted dot if (adj_dot[1] != 0 && adj_dot[0] != 0) { gc->SetBrush(*wxRED_BRUSH); DrawCoordinate(gc, adj_dot[1], adj_dot[0]); } } break; case ControllerEmu::GroupType::Buttons: { const unsigned int button_count = static_cast(g->control_group->controls.size()); std::vector bitmasks(button_count); // draw the shit gc->SetPen(*wxGREY_PEN); for (unsigned int n = 0; n < button_count; ++n) bitmasks[n] = (1 << n); unsigned int buttons = 0; ((ControllerEmu::Buttons*)g->control_group)->GetState(&buttons, bitmasks.data()); // Draw buttons in rows of 8 for (unsigned int row = 0; row < ceil((float)button_count / 8.0f); row++) { unsigned int buttons_to_draw = 8; if ((button_count - row * 8) <= 8) buttons_to_draw = button_count - row * 8; for (unsigned int n = 0; n < buttons_to_draw; ++n) { DrawButton(bitmasks, buttons, n, gc, g, row, null_matrix); } } } break; case ControllerEmu::GroupType::Triggers: { const unsigned int trigger_count = static_cast(g->control_group->controls.size()); std::vector trigs(trigger_count); // draw the shit gc->SetPen(*wxGREY_PEN); ControlState deadzone = g->control_group->numeric_settings[0]->GetValue(); ((ControllerEmu::Triggers*)g->control_group)->GetState(trigs.data()); for (unsigned int n = 0; n < trigger_count; ++n) { ControlState trig_r = g->control_group->controls[n]->control_ref->State(); // outline gc->SetBrush(*wxWHITE_BRUSH); gc->DrawRectangle(0, n * 12, 64, 14); // raw gc->SetBrush(*wxGREY_BRUSH); gc->DrawRectangle(0, n * 12, trig_r * 64, 14); // deadzone affected gc->SetBrush(*wxRED_BRUSH); gc->DrawRectangle(0, n * 12, trigs[n] * 64, 14); // text // We don't want the text to be scaled/zoomed gc->SetTransform(null_matrix); gc->DrawText(StrToWxStr(g->control_group->controls[n]->ui_name), 3 * g->m_scale, (n * 12 + 1) * g->m_scale); gc->SetTransform(scale_matrix); } // deadzone box gc->SetPen(*wxLIGHT_GREY_PEN); gc->SetBrush(*wxTRANSPARENT_BRUSH); gc->DrawRectangle(0, 0, deadzone * 64, trigger_count * 14); } break; case ControllerEmu::GroupType::MixedTriggers: { const unsigned int trigger_count = ((unsigned int)(g->control_group->controls.size() / 2)); // draw the shit gc->SetPen(*wxGREY_PEN); ControlState thresh = g->control_group->numeric_settings[0]->GetValue(); for (unsigned int n = 0; n < trigger_count; ++n) { gc->SetBrush(*wxRED_BRUSH); ControlState trig_d = g->control_group->controls[n]->control_ref->State(); ControlState trig_a = trig_d > thresh ? 1 : g->control_group->controls[n + trigger_count]->control_ref->State(); gc->DrawRectangle(0, n * 12, 64 + 20, 14); if (trig_d <= thresh) gc->SetBrush(*wxWHITE_BRUSH); gc->DrawRectangle(trig_a * 64, n * 12, 64 + 20, 14); gc->DrawRectangle(64, n * 12, 32, 14); // text // We don't want the text to be scaled/zoomed gc->SetTransform(null_matrix); gc->DrawText(StrToWxStr(g->control_group->controls[n + trigger_count]->ui_name), 3 * g->m_scale, (n * 12 + 1) * g->m_scale); gc->DrawText(StrToWxStr(std::string(1, g->control_group->controls[n]->ui_name[0])), (64 + 3) * g->m_scale, (n * 12 + 1) * g->m_scale); gc->SetTransform(scale_matrix); } // threshold box gc->SetPen(*wxLIGHT_GREY_PEN); gc->SetBrush(*wxTRANSPARENT_BRUSH); gc->DrawRectangle(thresh * 64, 0, 128, trigger_count * 14); } break; case ControllerEmu::GroupType::Slider: { const ControlState deadzone = g->control_group->numeric_settings[0]->GetValue(); ControlState state = g->control_group->controls[1]->control_ref->State() - g->control_group->controls[0]->control_ref->State(); gc->SetPen(*wxTRANSPARENT_PEN); gc->SetBrush(*wxGREY_BRUSH); gc->DrawRectangle(31 + state * 30, 0, 2, 14); ControlState adj_state; ((ControllerEmu::Slider*)g->control_group)->GetState(&adj_state); if (state) { gc->SetBrush(*wxRED_BRUSH); gc->DrawRectangle(31 + adj_state * 30, 0, 2, 14); } // deadzone box gc->SetPen(*wxLIGHT_GREY_PEN); gc->SetBrush(*wxTRANSPARENT_BRUSH); gc->DrawRectangle(32 - deadzone * 32, 0, deadzone * 64, 14); } break; default: break; } gc->SetTransform(null_matrix); } static void DrawBorder(wxGraphicsContext* gc, double scale) { double pen_width = std::round(scale); // Pen width = 1px * scale // Use the window caption bar color as a safe accent color. wxPen border_pen(wxSystemSettings::GetColour(wxSYS_COLOUR_ACTIVECAPTION), static_cast(pen_width)); border_pen.SetCap(wxCAP_PROJECTING); double width, height; gc->GetSize(&width, &height); wxGraphicsPath path = gc->CreatePath(); path.AddRectangle(pen_width / 2, pen_width / 2, width - pen_width, height - pen_width); gc->SetPen(border_pen); gc->StrokePath(path); } void InputConfigDialog::UpdateBitmaps(wxTimerEvent& WXUNUSED(event)) { wxFont small_font(6, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD); const wxColour font_color{0xB8B8B8}; g_controller_interface.UpdateInput(); wxMemoryDC dc; const auto lock = ControllerEmu::EmulatedController::GetStateLock(); for (ControlGroupBox* g : control_groups) { // Only if this control group has a bitmap if (!g->static_bitmap) continue; wxBitmap bitmap(g->static_bitmap->GetBitmap()); // NOTE: Selecting the bitmap inherits the bitmap's ScaleFactor onto the DC as well. dc.SelectObjectAsSource(bitmap); dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); #ifdef __WXGTK20__ int dc_height = 0; dc.SetFont(small_font); dc.GetTextExtent(g->control_group->name, nullptr, &dc_height); #endif std::unique_ptr gc{wxGraphicsContext::Create(dc)}; gc->DisableOffset(); gc->SetFont(small_font, font_color); #ifdef __WXGTK20__ double gc_height = 0; gc->GetTextExtent(g->control_group->name, nullptr, &gc_height); // On GTK2, wx creates a new empty Cairo/Pango context for the graphics context instead // of reusing the wxMemoryDC one, this causes it to forget the screen DPI so fonts stop // scaling, we need to scale it manually instead. if (std::ceil(gc_height) < dc_height) { wxFont fixed_font(small_font); fixed_font.SetPointSize(static_cast(fixed_font.GetPointSize() * g->m_scale)); gc->SetFont(fixed_font, font_color); } #endif DrawControlGroupBox(gc.get(), g); DrawBorder(gc.get(), g->m_scale); // label for sticks and stuff if (g->HasBitmapHeading()) gc->DrawText(StrToWxStr(g->control_group->name).Upper(), 4 * g->m_scale, 2 * g->m_scale); gc.reset(); dc.SelectObject(wxNullBitmap); g->static_bitmap->SetBitmap(bitmap); } }