xref: /haiku/src/preferences/input/MouseView.cpp (revision f60531661beee9c2bcde5df0e6b821dfc0bdfc24)
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 static const int32 kOneButtonOffsets[4]
39 	= { 0, kMouseDownWidth };
40 static const int32 kTwoButtonOffsets[4]
41 	= { 0, kMouseDownWidth / 2, kMouseDownWidth };
42 static const int32 kThreeButtonOffsets[4]
43 	= { 0, kMouseDownWidth / 3 + 1, kMouseDownWidth / 3 * 2, kMouseDownWidth };
44 
45 static const rgb_color kButtonTextColor = { 0, 0, 0, 255 };
46 static const rgb_color kMouseShadowColor = { 100, 100, 100, 128 };
47 static const rgb_color kMouseBodyTopColor = { 0xed, 0xed, 0xed, 255 };
48 static const rgb_color kMouseBodyBottomColor = { 0x85, 0x85, 0x85, 255 };
49 static const rgb_color kMouseOutlineColor = { 0x51, 0x51, 0x51, 255 };
50 static const rgb_color kMouseButtonOutlineColor = { 0xa0, 0xa0, 0xa0, 255 };
51 static const rgb_color kButtonPressedColor = { 110, 110, 110, 110 };
52 
53 
54 static const int32*
55 getButtonOffsets(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 
69 static uint32
70 getMappingNumber(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 
83 MouseView::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 
96 MouseView::~MouseView()
97 {
98 }
99 
100 
101 void
102 MouseView::SetMouseType(int32 type)
103 {
104 	fType = type;
105 	Invalidate();
106 }
107 
108 
109 void
110 MouseView::MouseMapUpdated()
111 {
112 	Invalidate();
113 }
114 
115 
116 void
117 MouseView::UpdateFromSettings()
118 {
119 	SetMouseType(fSettings.MouseType());
120 }
121 
122 
123 void
124 MouseView::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 
133 void
134 MouseView::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 
148 void
149 MouseView::MouseUp(BPoint)
150 {
151 	fButtons = 0;
152 	Invalidate(_ButtonsRect());
153 	fOldButtons = fButtons;
154 }
155 
156 
157 void
158 MouseView::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 
212 void
213 MouseView::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 
315 BRect
316 MouseView::_ButtonsRect() const
317 {
318 	return BRect(0, fScaling * kButtonTop, fScaling * kMouseDownWidth,
319 			fScaling * (kButtonTop + kMouseDownHeight));
320 }
321 
322 
323 BRect
324 MouseView::_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 
332 int32
333 MouseView::_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 
350 void
351 MouseView::_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 }