dolphin/Source/Core/DolphinWX/Input/InputConfigDiagBitmaps.cpp
Lioncash 3a66f2c008 ControllerEmu: Move into its own directory
ControllerEmu is a massive class with a lot of nested public classes.

The only reason these are nested is because the outer class acts as a
namespace. There's no reason to keep these classes nested just for that.

Keeping these classes nested makes it impossible to forward declare them, which leads to quite a few includes in other headers, making compilation take
longer.

This moves the source files to their own directory so classes can be
separated as necessary to their own source files, and be namespaced under the
ControllerEmu namespace.
2017-02-07 22:12:06 -05:00

539 lines
16 KiB
C++

// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <cmath>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include <wx/bitmap.h>
#include <wx/brush.h>
#include <wx/colour.h>
#include <wx/dcmemory.h>
#include <wx/font.h>
#include <wx/graphics.h>
#include <wx/notebook.h>
#include <wx/pen.h>
#include <wx/settings.h>
#include <wx/statbmp.h>
#include "DolphinWX/Input/InputConfigDiag.h"
#include "DolphinWX/WxUtils.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<unsigned int>& 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]->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 GROUP_TYPE_TILT:
case GROUP_TYPE_STICK:
case GROUP_TYPE_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 GROUP_TYPE_STICK:
((ControllerEmu::AnalogStick*)g->control_group)->GetState(&x, &y);
break;
case GROUP_TYPE_TILT:
((ControllerEmu::Tilt*)g->control_group)->GetState(&x, &y);
break;
case GROUP_TYPE_CURSOR:
((ControllerEmu::Cursor*)g->control_group)->GetState(&x, &y, &z);
break;
}
// ir cursor forward movement
if (GROUP_TYPE_CURSOR == g->control_group->type)
{
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 (GROUP_TYPE_STICK == g->control_group->type)
{
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 (GROUP_TYPE_CURSOR != g->control_group->type)
{
int deadzone_idx = g->control_group->type == GROUP_TYPE_STICK ? 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 GROUP_TYPE_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 GROUP_TYPE_BUTTONS:
{
const unsigned int button_count = static_cast<unsigned int>(g->control_group->controls.size());
std::vector<unsigned int> 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 GROUP_TYPE_TRIGGERS:
{
const unsigned int trigger_count = static_cast<unsigned int>(g->control_group->controls.size());
std::vector<ControlState> 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]->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 GROUP_TYPE_MIXED_TRIGGERS:
{
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]->name), 3 * g->m_scale,
(n * 12 + 1) * g->m_scale);
gc->DrawText(StrToWxStr(std::string(1, g->control_group->controls[n]->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 GROUP_TYPE_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<int>(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;
auto lock = ControllerEmu::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<wxGraphicsContext> 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<int>(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);
}
}