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
38static const int32 kOneButtonOffsets[4]
39	= { 0, kMouseDownWidth };
40static const int32 kTwoButtonOffsets[4]
41	= { 0, kMouseDownWidth / 2, kMouseDownWidth };
42static const int32 kThreeButtonOffsets[4]
43	= { 0, kMouseDownWidth / 3 + 1, kMouseDownWidth / 3 * 2, kMouseDownWidth };
44
45static const rgb_color kButtonTextColor = { 0, 0, 0, 255 };
46static const rgb_color kMouseShadowColor = { 100, 100, 100, 128 };
47static const rgb_color kMouseBodyTopColor = { 0xed, 0xed, 0xed, 255 };
48static const rgb_color kMouseBodyBottomColor = { 0x85, 0x85, 0x85, 255 };
49static const rgb_color kMouseOutlineColor = { 0x51, 0x51, 0x51, 255 };
50static const rgb_color kMouseButtonOutlineColor = { 0xa0, 0xa0, 0xa0, 255 };
51static const rgb_color kButtonPressedColor = { 110, 110, 110, 110 };
52
53
54static const int32*
55getButtonOffsets(int32 type)
56{
57	switch (type) {
58		case 1:
59			return kOneButtonOffsets;
60		case 2:
61			return kTwoButtonOffsets;
62		case 3:
63		default:
64			return kThreeButtonOffsets;
65	}
66}
67
68
69static uint32
70getMappingNumber(int32 mapping)
71{
72	switch (mapping) {
73		case B_PRIMARY_MOUSE_BUTTON:
74			return 0;
75		case B_SECONDARY_MOUSE_BUTTON:
76			return 1;
77		case B_TERTIARY_MOUSE_BUTTON:
78			return 2;
79	}
80	return 0;
81}
82
83MouseView::MouseView(const MouseSettings &settings)
84	:
85	BView("Mouse", B_PULSE_NEEDED | B_WILL_DRAW),
86	fSettings(settings),
87	fType(-1),
88	fButtons(0),
89	fOldButtons(0)
90{
91	SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
92	fScaling = std::max(1.0f, be_plain_font->Size() / 12.0f);
93}
94
95
96MouseView::~MouseView()
97{
98}
99
100
101void
102MouseView::SetMouseType(int32 type)
103{
104	fType = type;
105	Invalidate();
106}
107
108
109void
110MouseView::MouseMapUpdated()
111{
112	Invalidate();
113}
114
115
116void
117MouseView::UpdateFromSettings()
118{
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
170	// Get the current clipping region before requesting any updates.
171	// Otherwise those parts would be excluded from the region.
172	BRegion clipping;
173	GetClippingRegion(&clipping);
174
175	if (fOldButtons != fButtons) {
176		Invalidate(_ButtonsRect());
177		fOldButtons = fButtons;
178	}
179
180	const int32* offset = getButtonOffsets(fType);
181	int32 button = -1;
182	for (int32 i = 0; i <= fType; i++) {
183		if (_ButtonRect(offset, i).Contains(where)) {
184			button = i;
185			break;
186		}
187	}
188	if (button < 0)
189		return;
190
191	if (clipping.Contains(where)) {
192		button = _ConvertFromVisualOrder(button);
193
194		BPopUpMenu menu("Mouse Map Menu");
195		BMessage message(kMsgMouseMap);
196		message.AddInt32("button", button);
197
198		menu.AddItem(new BMenuItem("1", new BMessage(message)));
199		menu.AddItem(new BMenuItem("2", new BMessage(message)));
200		menu.AddItem(new BMenuItem("3", new BMessage(message)));
201
202		menu.ItemAt(getMappingNumber(fSettings.Mapping(button)))
203			->SetMarked(true);
204		menu.SetTargetForItems(Window());
205
206		ConvertToScreen(&where);
207		menu.Go(where, true);
208	}
209}
210
211
212void
213MouseView::Draw(BRect updateFrame)
214{
215	SetDrawingMode(B_OP_ALPHA);
216	SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
217	SetScale(fScaling * 1.8);
218
219	BShape mouseShape;
220	mouseShape.MoveTo(BPoint(16, 12));
221	// left
222	BPoint control[3] = { BPoint(12, 16), BPoint(8, 64), BPoint(32, 64) };
223	mouseShape.BezierTo(control);
224	// right
225	BPoint control2[3] = { BPoint(56, 64), BPoint(52, 16), BPoint(48, 12) };
226	mouseShape.BezierTo(control2);
227	// top
228	BPoint control3[3] = { BPoint(44, 8), BPoint(20, 8), BPoint(16, 12) };
229	mouseShape.BezierTo(control3);
230	mouseShape.Close();
231
232	// Draw the shadow
233	SetOrigin(-17 * fScaling, -11 * fScaling);
234	SetHighColor(kMouseShadowColor);
235	FillShape(&mouseShape, B_SOLID_HIGH);
236
237	// Draw the body
238	SetOrigin(-21 * fScaling, -14 * fScaling);
239	BGradientRadial bodyGradient(28, 24, 128);
240	bodyGradient.AddColor(kMouseBodyTopColor, 0);
241	bodyGradient.AddColor(kMouseBodyBottomColor, 255);
242
243	FillShape(&mouseShape, bodyGradient);
244
245	// Draw the outline
246	SetPenSize(1 / 1.8 / fScaling);
247	SetDrawingMode(B_OP_OVER);
248	SetHighColor(kMouseOutlineColor);
249
250	StrokeShape(&mouseShape, B_SOLID_HIGH);
251
252	// bottom button border
253	BShape buttonsOutline;
254	buttonsOutline.MoveTo(BPoint(13, 27));
255	BPoint control4[] = { BPoint(18, 30), BPoint(46, 30), BPoint(51, 27) };
256	buttonsOutline.BezierTo(control4);
257
258	SetHighColor(kMouseButtonOutlineColor);
259	StrokeShape(&buttonsOutline, B_SOLID_HIGH);
260
261	SetScale(1);
262	SetOrigin(0, 0);
263
264	// Separator between the buttons
265	const int32* offset = getButtonOffsets(fType);
266	for (int32 i = 1; i < fType; i++) {
267		BRect buttonRect = _ButtonRect(offset, i);
268		StrokeLine(buttonRect.LeftTop(), buttonRect.LeftBottom());
269	}
270
271	mouse_map map;
272	fSettings.Mapping(map);
273
274	SetDrawingMode(B_OP_OVER);
275
276	if (fButtons != 0)
277		ClipToPicture(&fButtonsPicture, B_ORIGIN, false);
278
279	for (int32 i = 0; i < fType; i++) {
280		// draw mapping number centered over the button
281
282		bool pressed = (fButtons & map.button[_ConvertFromVisualOrder(i)]) != 0;
283			// is button currently pressed?
284		if (pressed) {
285			SetDrawingMode(B_OP_ALPHA);
286			SetHighColor(kButtonPressedColor);
287			FillRect(_ButtonRect(offset, i));
288		}
289
290		BRect border(fScaling * (offset[i] + 1), fScaling * (kButtonTop + 5),
291			fScaling * offset[i + 1] - 1,
292			fScaling * (kButtonTop + kMouseDownHeight - 4));
293		if (i == 0)
294			border.left += fScaling * 5;
295		if (i == fType - 1)
296			border.right -= fScaling * 4;
297
298		char number[2] = {0};
299		number[0] = getMappingNumber(map.button[_ConvertFromVisualOrder(i)])
300			+ '1';
301
302		SetDrawingMode(B_OP_OVER);
303		SetHighColor(kButtonTextColor);
304		DrawString(number, BPoint(
305			border.left + (border.Width() - StringWidth(number)) / 2,
306			border.top + fDigitBaseline
307				+ (border.IntegerHeight() - fDigitHeight) / 2));
308	}
309
310	if (fButtons != 0)
311		ClipToPicture(NULL);
312}
313
314
315BRect
316MouseView::_ButtonsRect() const
317{
318	return BRect(0, fScaling * kButtonTop, fScaling * kMouseDownWidth,
319			fScaling * (kButtonTop + kMouseDownHeight));
320}
321
322
323BRect
324MouseView::_ButtonRect(const int32* offsets, int index) const
325{
326	return BRect(fScaling * offsets[index], fScaling * kButtonTop,
327		fScaling * offsets[index + 1] - 1,
328		fScaling * (kButtonTop + kMouseDownHeight));
329}
330
331
332int32
333MouseView::_ConvertFromVisualOrder(int32 i)
334{
335	if (fType < 3)
336		return i;
337
338	switch (i) {
339		case 0:
340		default:
341			return 0;
342		case 1:
343			return 2;
344		case 2:
345			return 1;
346	}
347}
348
349
350void
351MouseView::_CreateButtonsPicture()
352{
353	BeginPicture(&fButtonsPicture);
354	SetScale(1.8 * fScaling);
355	SetOrigin(-21 * fScaling, -14 * fScaling);
356
357	BShape mouseShape;
358	mouseShape.MoveTo(BPoint(48, 12));
359	// top
360	BPoint control3[3] = { BPoint(44, 8), BPoint(20, 8), BPoint(16, 12) };
361	mouseShape.BezierTo(control3);
362	// left
363	BPoint control[3] = { BPoint(12, 16), BPoint(13, 27), BPoint(13, 27) };
364	mouseShape.BezierTo(control);
365	// bottom
366	BPoint control4[3] = { BPoint(18, 30), BPoint(46, 30), BPoint(51, 27) };
367	mouseShape.BezierTo(control4);
368	// right
369	BPoint control2[3] = { BPoint(51, 27), BPoint(50, 14), BPoint(48, 12) };
370	mouseShape.BezierTo(control2);
371
372	mouseShape.Close();
373
374	SetHighColor(255, 0, 0, 255);
375	FillShape(&mouseShape, B_SOLID_HIGH);
376
377	EndPicture();
378	SetScale(1);
379}