1/*
2 * Copyright 2019, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Author:
6 *		Preetpal Kaur <preetpalok123@gmail.com>
7 */
8
9
10#include "MouseView.h"
11
12#include <algorithm>
13
14#include <Box.h>
15#include <Button.h>
16#include <Debug.h>
17#include <GradientLinear.h>
18#include <GradientRadial.h>
19#include <MenuField.h>
20#include <MenuItem.h>
21#include <PopUpMenu.h>
22#include <Region.h>
23#include <Shape.h>
24#include <Slider.h>
25#include <TextControl.h>
26#include <TranslationUtils.h>
27#include <TranslatorFormats.h>
28#include <Window.h>
29
30#include "InputConstants.h"
31#include "MouseSettings.h"
32
33
34static const int32 kButtonTop = 3;
35static const int32 kMouseDownWidth = 72;
36static const int32 kMouseDownHeight = 35;
37
38#define W kMouseDownWidth / 100
39static const int32 kButtonOffsets[][7] = {
40	{ 0, 100 * W },
41	{ 0, 50 * W, 100 * W },
42	{ 0, 35 * W, 65 * W, 100 * W },
43	{ 0, 25 * W, 50 * W, 75 * W, 100 * W },
44	{ 0, 20 * W, 40 * W, 60 * W, 80 * W, 100 * W },
45	{ 0, 19 * W, 34 * W, 50 * W, 66 * W, 82 * W, 100 * W }
46};
47#undef W
48
49static const rgb_color kButtonTextColor = {0, 0, 0, 255};
50static const rgb_color kMouseShadowColor = {100, 100, 100, 128};
51static const rgb_color kMouseBodyTopColor = {0xed, 0xed, 0xed, 255};
52static const rgb_color kMouseBodyBottomColor = {0x85, 0x85, 0x85, 255};
53static const rgb_color kMouseOutlineColor = {0x51, 0x51, 0x51, 255};
54static const rgb_color kMouseButtonOutlineColor = {0xa0, 0xa0, 0xa0, 255};
55static const rgb_color kButtonPressedColor = {110, 110, 110, 110};
56
57
58static const int32*
59getButtonOffsets(int32 type)
60{
61	if ((type - 1) >= (int32)B_COUNT_OF(kButtonOffsets))
62		return kButtonOffsets[2];
63	return kButtonOffsets[type - 1];
64}
65
66
67static uint32
68getMappingNumber(uint32 mapping)
69{
70	if (mapping == 0)
71		return 0;
72
73	int i;
74	for (i = 0; mapping != 1; i++)
75		mapping >>= 1;
76
77	return i;
78}
79
80
81MouseView::MouseView(const MouseSettings& settings)
82	:
83	BView("Mouse", B_PULSE_NEEDED | B_WILL_DRAW),
84	fSettings(settings),
85	fType(-1),
86	fButtons(0),
87	fOldButtons(0)
88{
89	SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
90	fScaling = std::max(1.0f, be_plain_font->Size() / 7.0f);
91}
92
93
94MouseView::~MouseView()
95{
96}
97
98
99void
100MouseView::SetMouseType(int32 type)
101{
102	fType = type;
103	Invalidate();
104}
105
106
107void
108MouseView::MouseMapUpdated()
109{
110	Invalidate();
111}
112
113
114void
115MouseView::UpdateFromSettings()
116{
117	if (fSettings.MouseType() > 6)
118		debugger("Mouse type is invalid");
119	SetMouseType(fSettings.MouseType());
120}
121
122
123void
124MouseView::GetPreferredSize(float* _width, float* _height)
125{
126	if (_width != NULL)
127		*_width = fScaling * (kMouseDownWidth + 2);
128	if (_height != NULL)
129		*_height = fScaling * 104;
130}
131
132
133void
134MouseView::AttachedToWindow()
135{
136	AdoptParentColors();
137
138	UpdateFromSettings();
139	_CreateButtonsPicture();
140
141	font_height fontHeight;
142	GetFontHeight(&fontHeight);
143	fDigitHeight = int32(ceilf(fontHeight.ascent) + ceilf(fontHeight.descent));
144	fDigitBaseline = int32(ceilf(fontHeight.ascent));
145}
146
147
148void
149MouseView::MouseUp(BPoint)
150{
151	fButtons = 0;
152	Invalidate(_ButtonsRect());
153	fOldButtons = fButtons;
154}
155
156
157void
158MouseView::MouseDown(BPoint where)
159{
160	BMessage* mouseMsg = Window()->CurrentMessage();
161	fButtons = mouseMsg->FindInt32("buttons");
162	int32 modifiers = mouseMsg->FindInt32("modifiers");
163	if (modifiers & B_CONTROL_KEY) {
164		if (modifiers & B_COMMAND_KEY)
165			fButtons = B_TERTIARY_MOUSE_BUTTON;
166		else
167			fButtons = B_SECONDARY_MOUSE_BUTTON;
168	}
169	// Get the current clipping region before requesting any updates.
170	// Otherwise those parts would be excluded from the region.
171	BRegion clipping;
172	GetClippingRegion(&clipping);
173
174	if (fOldButtons != fButtons) {
175		Invalidate(_ButtonsRect());
176		fOldButtons = fButtons;
177	}
178
179	const int32* offset = getButtonOffsets(fType);
180	int32 button = -1;
181	for (int32 i = 0; i <= fType; i++) {
182		if (_ButtonRect(offset, i).Contains(where)) {
183			button = i;
184			break;
185		}
186	}
187	if (button < 0)
188		return;
189
190	if (clipping.Contains(where)) {
191		button = _ConvertFromVisualOrder(button);
192
193		BPopUpMenu menu("Mouse Map Menu");
194		BMessage message(kMsgMouseMap);
195		message.AddInt32("button", button);
196
197		for (int i = 1; i < 7; i++) {
198			char tmp[2];
199			sprintf(tmp, "%d", i);
200			menu.AddItem(new BMenuItem(tmp, new BMessage(message)));
201		}
202
203		int32 mapping = fSettings.Mapping(button);
204		BMenuItem* item = menu.ItemAt(getMappingNumber(mapping));
205		if (item)
206			item->SetMarked(true);
207		menu.SetTargetForItems(Window());
208
209		ConvertToScreen(&where);
210		menu.Go(where, true);
211	}
212}
213
214
215void
216MouseView::Draw(BRect updateFrame)
217{
218	SetDrawingMode(B_OP_ALPHA);
219	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
220	SetScale(fScaling * 1.8);
221
222	BShape mouseShape;
223	mouseShape.MoveTo(BPoint(16, 12));
224	// left
225	BPoint control[3] = {BPoint(12, 16), BPoint(8, 64), BPoint(32, 64)};
226	mouseShape.BezierTo(control);
227	// right
228	BPoint control2[3] = {BPoint(56, 64), BPoint(52, 16), BPoint(48, 12)};
229	mouseShape.BezierTo(control2);
230	// top
231	BPoint control3[3] = {BPoint(44, 8), BPoint(20, 8), BPoint(16, 12)};
232	mouseShape.BezierTo(control3);
233	mouseShape.Close();
234
235	// Draw the shadow
236	SetOrigin(-17 * fScaling, -11 * fScaling);
237	SetHighColor(kMouseShadowColor);
238	FillShape(&mouseShape, B_SOLID_HIGH);
239
240	// Draw the body
241	SetOrigin(-21 * fScaling, -14 * fScaling);
242	BGradientRadial bodyGradient(28, 24, 128);
243	bodyGradient.AddColor(kMouseBodyTopColor, 0);
244	bodyGradient.AddColor(kMouseBodyBottomColor, 255);
245
246	FillShape(&mouseShape, bodyGradient);
247
248	// Draw the outline
249	SetPenSize(1 / 1.8 / fScaling);
250	SetDrawingMode(B_OP_OVER);
251	SetHighColor(kMouseOutlineColor);
252
253	StrokeShape(&mouseShape, B_SOLID_HIGH);
254
255	// bottom button border
256	BShape buttonsOutline;
257	buttonsOutline.MoveTo(BPoint(13, 27));
258	BPoint control4[] = {BPoint(18, 30), BPoint(46, 30), BPoint(51, 27)};
259	buttonsOutline.BezierTo(control4);
260
261	SetHighColor(kMouseButtonOutlineColor);
262	StrokeShape(&buttonsOutline, B_SOLID_HIGH);
263
264	SetScale(1);
265	SetOrigin(0, 0);
266
267	mouse_map map;
268	fSettings.Mapping(map);
269
270	SetDrawingMode(B_OP_OVER);
271
272	// All button drawing is clipped to the outline of the buttons area,
273	// simplifying the code below as it can overdraw things.
274	ClipToPicture(&fButtonsPicture, B_ORIGIN, false);
275
276	// Separator between the buttons
277	const int32* offset = getButtonOffsets(fType);
278	for (int32 i = 1; i < fType; i++) {
279		BRect buttonRect = _ButtonRect(offset, i);
280		StrokeLine(buttonRect.LeftTop(), buttonRect.LeftBottom());
281	}
282
283	for (int32 i = 0; i < fType; i++) {
284		// draw mapping number centered over the button
285
286		bool pressed = (fButtons & map.button[_ConvertFromVisualOrder(i)]) != 0;
287		// is button currently pressed?
288		if (pressed) {
289			SetDrawingMode(B_OP_ALPHA);
290			SetHighColor(kButtonPressedColor);
291			FillRect(_ButtonRect(offset, i));
292		}
293
294		BRect border(fScaling * (offset[i] + 1), fScaling * (kButtonTop + 5),
295			fScaling * offset[i + 1] - 1,
296			fScaling * (kButtonTop + kMouseDownHeight - 4));
297		if (i == 0)
298			border.left += fScaling * 5;
299		if (i == fType - 1)
300			border.right -= fScaling * 4;
301
302		char label[2] = {0};
303		int32 number = getMappingNumber(map.button[_ConvertFromVisualOrder(i)]);
304		label[0] = number + '1';
305
306		SetDrawingMode(B_OP_OVER);
307		SetHighColor(kButtonTextColor);
308		DrawString(label,
309			BPoint(border.left + (border.Width() - StringWidth(label)) / 2,
310				border.top + fDigitBaseline
311					+ (border.IntegerHeight() - fDigitHeight) / 2));
312	}
313
314	ClipToPicture(NULL);
315}
316
317
318BRect
319MouseView::_ButtonsRect() const
320{
321	return BRect(0, fScaling * kButtonTop, fScaling * kMouseDownWidth,
322		fScaling * (kButtonTop + kMouseDownHeight));
323}
324
325
326BRect
327MouseView::_ButtonRect(const int32* offsets, int index) const
328{
329	return BRect(fScaling * offsets[index], fScaling * kButtonTop,
330		fScaling * offsets[index + 1] - 1,
331		fScaling * (kButtonTop + kMouseDownHeight));
332}
333
334
335/** The buttons on a mouse are normally 1 (left), 2 (right), 3 (middle) so
336 * we need to reorder them */
337int32
338MouseView::_ConvertFromVisualOrder(int32 i)
339{
340	if (fType < 3)
341		return i;
342
343	switch (i) {
344		case 0:
345			return 0;
346		case 1:
347			return 2;
348		case 2:
349			return 1;
350		default:
351			return i;
352	}
353}
354
355
356void
357MouseView::_CreateButtonsPicture()
358{
359	BeginPicture(&fButtonsPicture);
360	SetScale(1.8 * fScaling);
361	SetOrigin(-21 * fScaling, -14 * fScaling);
362
363	BShape mouseShape;
364	mouseShape.MoveTo(BPoint(48, 12));
365	// top
366	BPoint control3[3] = {BPoint(44, 8), BPoint(20, 8), BPoint(16, 12)};
367	mouseShape.BezierTo(control3);
368	// left
369	BPoint control[3] = {BPoint(12, 16), BPoint(13, 27), BPoint(13, 27)};
370	mouseShape.BezierTo(control);
371	// bottom
372	BPoint control4[3] = {BPoint(18, 30), BPoint(46, 30), BPoint(51, 27)};
373	mouseShape.BezierTo(control4);
374	// right
375	BPoint control2[3] = {BPoint(51, 27), BPoint(50, 14), BPoint(48, 12)};
376	mouseShape.BezierTo(control2);
377
378	mouseShape.Close();
379
380	SetHighColor(255, 0, 0, 255);
381	FillShape(&mouseShape, B_SOLID_HIGH);
382
383	EndPicture();
384	SetOrigin(0, 0);
385	SetScale(1);
386}
387