xref: /haiku/src/preferences/input/MouseView.cpp (revision 87f4776937505e3014251c9c3434be78ae29d7d0)
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[][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 
49 static const rgb_color kButtonTextColor = { 0, 0, 0, 255 };
50 static const rgb_color kMouseShadowColor = { 100, 100, 100, 128 };
51 static const rgb_color kMouseBodyTopColor = { 0xed, 0xed, 0xed, 255 };
52 static const rgb_color kMouseBodyBottomColor = { 0x85, 0x85, 0x85, 255 };
53 static const rgb_color kMouseOutlineColor = { 0x51, 0x51, 0x51, 255 };
54 static const rgb_color kMouseButtonOutlineColor = { 0xa0, 0xa0, 0xa0, 255 };
55 static const rgb_color kButtonPressedColor = { 110, 110, 110, 110 };
56 
57 
58 static const int32*
59 getButtonOffsets(int32 type)
60 {
61 	if ((type - 1) >= (int32)B_COUNT_OF(kButtonOffsets))
62 		return kButtonOffsets[2];
63 	return kButtonOffsets[type - 1];
64 }
65 
66 
67 static uint32
68 getMappingNumber(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 MouseView::MouseView(const MouseSettings &settings)
81 	:
82 	BView("Mouse", B_PULSE_NEEDED | B_WILL_DRAW),
83 	fSettings(settings),
84 	fType(-1),
85 	fButtons(0),
86 	fOldButtons(0)
87 {
88 	SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
89 	fScaling = std::max(1.0f, be_plain_font->Size() / 7.0f);
90 }
91 
92 
93 MouseView::~MouseView()
94 {
95 }
96 
97 
98 void
99 MouseView::SetMouseType(int32 type)
100 {
101 	fType = type;
102 	Invalidate();
103 }
104 
105 
106 void
107 MouseView::MouseMapUpdated()
108 {
109 	Invalidate();
110 }
111 
112 
113 void
114 MouseView::UpdateFromSettings()
115 {
116 	if (fSettings.MouseType() > 6)
117 		debugger("Mouse type is invalid");
118 	SetMouseType(fSettings.MouseType());
119 }
120 
121 
122 void
123 MouseView::GetPreferredSize(float* _width, float* _height)
124 {
125 	if (_width != NULL)
126 		*_width = fScaling * (kMouseDownWidth + 2);
127 	if (_height != NULL)
128 		*_height = fScaling * 104;
129 }
130 
131 
132 void
133 MouseView::AttachedToWindow()
134 {
135 	AdoptParentColors();
136 
137 	UpdateFromSettings();
138 	_CreateButtonsPicture();
139 
140 	font_height fontHeight;
141 	GetFontHeight(&fontHeight);
142 	fDigitHeight = int32(ceilf(fontHeight.ascent) + ceilf(fontHeight.descent));
143 	fDigitBaseline = int32(ceilf(fontHeight.ascent));
144 }
145 
146 
147 void
148 MouseView::MouseUp(BPoint)
149 {
150 	fButtons = 0;
151 	Invalidate(_ButtonsRect());
152 	fOldButtons = fButtons;
153 }
154 
155 
156 void
157 MouseView::MouseDown(BPoint where)
158 {
159 	BMessage *mouseMsg = Window()->CurrentMessage();
160 	fButtons = mouseMsg->FindInt32("buttons");
161 	int32 modifiers = mouseMsg->FindInt32("modifiers");
162 	if (modifiers & B_CONTROL_KEY) {
163 		if (modifiers & B_COMMAND_KEY)
164 			fButtons = B_TERTIARY_MOUSE_BUTTON;
165 		else
166 			fButtons = B_SECONDARY_MOUSE_BUTTON;
167 	}
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 
215 void
216 MouseView::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, BPoint(
309 			border.left + (border.Width() - StringWidth(label)) / 2,
310 			border.top + fDigitBaseline
311 				+ (border.IntegerHeight() - fDigitHeight) / 2));
312 	}
313 
314 	ClipToPicture(NULL);
315 }
316 
317 
318 BRect
319 MouseView::_ButtonsRect() const
320 {
321 	return BRect(0, fScaling * kButtonTop, fScaling * kMouseDownWidth,
322 			fScaling * (kButtonTop + kMouseDownHeight));
323 }
324 
325 
326 BRect
327 MouseView::_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 */
337 int32
338 MouseView::_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 
356 void
357 MouseView::_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