1 // Copyright 2019 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h"
6 
7 #include <array>
8 #include <cassert>
9 
10 #include "Common/BitUtils.h"
11 #include "Common/Common.h"
12 #include "Common/CommonTypes.h"
13 #include "Core/HW/WiimoteEmu/WiimoteEmu.h"
14 
15 #include "InputCommon/ControllerEmu/Control/Input.h"
16 #include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h"
17 #include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
18 #include "InputCommon/ControllerEmu/ControlGroup/Triggers.h"
19 
20 namespace WiimoteEmu
21 {
22 constexpr std::array<u8, 6> udraw_tablet_id{{0xff, 0x00, 0xa4, 0x20, 0x01, 0x12}};
23 
24 constexpr std::array<u8, 2> udraw_tablet_button_bitmasks{{
25     UDrawTablet::BUTTON_ROCKER_UP,
26     UDrawTablet::BUTTON_ROCKER_DOWN,
27 }};
28 
29 constexpr std::array<const char*, 2> udraw_tablet_button_names{{
30     _trans("Rocker Up"),
31     _trans("Rocker Down"),
32 }};
33 
UDrawTablet()34 UDrawTablet::UDrawTablet() : Extension3rdParty("uDraw", _trans("uDraw GameTablet"))
35 {
36   // Buttons
37   groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
38   for (auto& button_name : udraw_tablet_button_names)
39   {
40     m_buttons->AddInput(ControllerEmu::Translate, button_name);
41   }
42 
43   // Stylus
44   groups.emplace_back(m_stylus = new ControllerEmu::AnalogStick(
45                           _trans("Stylus"), std::make_unique<ControllerEmu::SquareStickGate>(1.0)));
46 
47   // Touch
48   groups.emplace_back(m_touch = new ControllerEmu::Triggers(_trans("Touch")));
49   m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure"));
50 }
51 
Update()52 void UDrawTablet::Update()
53 {
54   DataFormat tablet_data = {};
55 
56   // Pressure:
57   // Min/Max values produced on my device: 0x08, 0xf2
58   // We're just gonna assume it's an old sensor and use the full byte range:
59   // Note: Pressure values are valid even when stylus is lifted.
60   constexpr u8 max_pressure = 0xff;
61 
62   const auto touch_state = m_touch->GetState();
63   tablet_data.pressure = static_cast<u8>(touch_state.data[0] * max_pressure);
64 
65   // Stylus X/Y:
66   // Min/Max X values (when touched) produced on my device: 0x4f, 0x7B3
67   // Drawing area edge (approx) X values on my device: 0x56, 0x7a5
68   // Min/Max Y values (when touched) produced on my device: 0x53, 0x5b4
69   // Drawing area edge (approx) Y values on my device: 0x5e, 0x5a5
70 
71   // Calibrated for "uDraw Studio: Instant Artist".
72   constexpr u16 min_x = 0x56;
73   constexpr u16 max_x = 0x780;
74   constexpr u16 min_y = 0x65;
75   constexpr u16 max_y = 0x5a5;
76   constexpr double center_x = (max_x + min_x) / 2.0;
77   constexpr double center_y = (max_y + min_y) / 2.0;
78 
79   // Neutral (lifted) stylus state:
80   u16 stylus_x = 0x7ff;
81   u16 stylus_y = 0x7ff;
82 
83   // TODO: Expose the lifted stylus state in the UI.
84   bool is_stylus_lifted = false;
85 
86   const auto stylus_state = m_stylus->GetState();
87 
88   if (!is_stylus_lifted)
89   {
90     stylus_x = u16(center_x + stylus_state.x * (max_x - center_x));
91     stylus_y = u16(center_y + stylus_state.y * (max_y - center_y));
92   }
93 
94   tablet_data.stylus_x1 = stylus_x & 0xff;
95   tablet_data.stylus_x2 = stylus_x >> 8;
96   tablet_data.stylus_y1 = stylus_y & 0xff;
97   tablet_data.stylus_y2 = stylus_y >> 8;
98 
99   // Buttons:
100   m_buttons->GetState(&tablet_data.buttons, udraw_tablet_button_bitmasks.data());
101 
102   // Flip button bits
103   constexpr u8 buttons_neutral_state = 0xfb;
104   tablet_data.buttons ^= buttons_neutral_state;
105 
106   // Always 0xff
107   tablet_data.unk = 0xff;
108 
109   Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data;
110 }
111 
Reset()112 void UDrawTablet::Reset()
113 {
114   EncryptedExtension::Reset();
115 
116   m_reg.identifier = udraw_tablet_id;
117 
118   // Both 0x20 and 0x30 calibration sections are just filled with 0xff on the real tablet:
119   m_reg.calibration.fill(0xff);
120 }
121 
GetGroup(UDrawTabletGroup group)122 ControllerEmu::ControlGroup* UDrawTablet::GetGroup(UDrawTabletGroup group)
123 {
124   switch (group)
125   {
126   case UDrawTabletGroup::Buttons:
127     return m_buttons;
128   case UDrawTabletGroup::Stylus:
129     return m_stylus;
130   case UDrawTabletGroup::Touch:
131     return m_touch;
132   default:
133     assert(false);
134     return nullptr;
135   }
136 }
137 
138 }  // namespace WiimoteEmu
139