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