1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 19 июл. 2017 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/tk/tk.h>
23 #include <ui/graphics.h>
24 
25 namespace lsp
26 {
27     namespace tk
28     {
29         const w_class_t LSPMarker::metadata = { "LSPMarker", &LSPGraphItem::metadata };
30 
LSPMarker(LSPDisplay * dpy)31         LSPMarker::LSPMarker(LSPDisplay *dpy): LSPGraphItem(dpy),
32             sColor(this)
33         {
34             nBasisID    = 0;
35             nParallelID = 1;
36             fValue      = 0.0f;
37             fLast       = 0.0f;
38             fOffset     = 0.0f;
39             fAngle      = 0.0f;
40             fDX         = 1.0f;
41             fDY         = 0.0f;
42             fMin        = -1.0f;
43             fMax        = 1.0f;
44             nWidth      = 1;
45             nCenter     = 0;
46             nBorder     = 0;
47             pClass      = &metadata;
48             nXFlags     = 0;
49             nMouseX     = 0; // debug
50             nMouseY     = 0; // debug
51 //            nDMouseX    = 0; // debug
52 //            nDMouseY    = 0; // debug
53             nMouseBtn   = 0;
54 
55             set_smooth(false);
56         }
57 
~LSPMarker()58         LSPMarker::~LSPMarker()
59         {
60         }
61 
init()62         status_t LSPMarker::init()
63         {
64             status_t result = LSPGraphItem::init();
65             if (result != STATUS_OK)
66                 return result;
67 
68             init_color(C_GRAPH_MARKER, &sColor);
69 
70             if (!sSlots.add(LSPSLOT_CHANGE))
71                 return STATUS_NO_MEM;
72 
73             return STATUS_OK;
74         }
75 
set_basis_id(size_t value)76         void LSPMarker::set_basis_id(size_t value)
77         {
78             if (nBasisID == value)
79                 return;
80             nBasisID = value;
81             query_draw();
82         }
83 
set_parallel_id(size_t value)84         void LSPMarker::set_parallel_id(size_t value)
85         {
86             if (nParallelID == value)
87                 return;
88             nParallelID = value;
89             query_draw();
90         }
91 
set_value(float value)92         void LSPMarker::set_value(float value)
93         {
94             if (fValue == value)
95                 return;
96             fValue = value;
97             query_draw();
98         }
99 
set_offset(float value)100         void LSPMarker::set_offset(float value)
101         {
102             if (fOffset == value)
103                 return;
104             fOffset = value;
105             query_draw();
106         }
107 
set_angle(float value)108         void LSPMarker::set_angle(float value)
109         {
110             if (fAngle == value)
111                 return;
112             fDX     = cosf(value);
113             fDY     = sinf(value);
114             fAngle  = value;
115             query_draw();
116         }
117 
set_direction(float dx,float dy)118         void LSPMarker::set_direction(float dx, float dy)
119         {
120             fDX         = dx;
121             fDY         = dy;
122             fAngle      = get_angle_2d(0.0f, 0.0f, dx, dy);
123 
124             query_draw();
125         }
126 
set_width(size_t value)127         void LSPMarker::set_width(size_t value)
128         {
129             if (nWidth == value)
130                 return;
131             nWidth = value;
132             query_draw();
133         }
134 
set_center(size_t value)135         void LSPMarker::set_center(size_t value)
136         {
137             if (nCenter == value)
138                 return;
139             nCenter = value;
140             query_draw();
141         }
142 
set_border(ssize_t value)143         void LSPMarker::set_border(ssize_t value)
144         {
145             if (nBorder == value)
146                 return;
147             nBorder = value;
148             query_draw();
149         }
150 
set_editable(bool value)151         void LSPMarker::set_editable(bool value)
152         {
153             size_t flags = nXFlags;
154             if (value)
155                 nXFlags     |= F_EDITABLE;
156             else
157                 nXFlags     &= ~F_EDITABLE;
158             if (flags != nXFlags)
159                 query_draw();
160         }
161 
set_minimum(float value)162         void LSPMarker::set_minimum(float value)
163         {
164             if (fMin == value)
165                 return;
166             fMin = value;
167             query_draw();
168         }
169 
set_maximum(float value)170         void LSPMarker::set_maximum(float value)
171         {
172             if (fMax == value)
173                 return;
174             fMax = value;
175             query_draw();
176         }
177 
render(ISurface * s,bool force)178         void LSPMarker::render(ISurface *s, bool force)
179         {
180             // Get graph
181             LSPGraph *cv        = graph();
182             if (cv == NULL)
183                 return;
184 
185             // Prepare palette
186             Color color(sColor);
187             color.scale_lightness(brightness());
188 
189             // Get basis
190             LSPAxis *basis      = cv->axis(nBasisID);
191             if (basis == NULL)
192                 return;
193             LSPAxis *parallel   = cv->axis(nParallelID);
194             if (parallel == NULL)
195                 return;
196 
197             float x = 0.0f, y = 0.0f;
198             cv->center(nCenter, &x, &y);
199 
200             // Translate point and get the coordinates of point that lays on the target line
201             if (!basis->apply(&x, &y, &fValue, 1))
202                 return;
203             if (fOffset != 0.0f)
204             {
205                 if (!parallel->apply(&x, &y, &fOffset, 1))
206                     return;
207             }
208 
209             // Get equation of the line that contains calculated point
210             float a, b, c;
211             float nx, ny;
212             float a2, b2, c2;
213 
214             if (fAngle == 0.0f)
215             {
216                 if (!parallel->parallel(x, y, a, b, c))
217                     return;
218                 if (nBorder != 0)
219                 {
220                     parallel->ortogonal_shift(x, y, nBorder, nx, ny);
221                     if (!parallel->parallel(nx, ny, a2, b2, c2))
222                         return;
223                 }
224             }
225             else
226             {
227                 if (!parallel->angle(x, y, fAngle, a, b, c))
228                     return;
229                 if (nBorder != 0)
230                 {
231                     parallel->rotate_shift(x, y, fAngle, nBorder, nx, ny);
232                     if (!parallel->angle(x, y, fAngle, a2, b2, c2))
233                         return;
234                 }
235             }
236 
237             // Draw line
238             bool aa = s->set_antialiasing(bSmooth);
239             Color col(sColor, 0.0f);
240 
241             ssize_t l_width = nWidth;
242             if (nXFlags & F_HIGHLIGHT)
243                 l_width += 2;
244 
245             if (nBorder != 0)
246             {
247                 IGradient *g = s->linear_gradient(x, y, nx, ny);
248                 if (g != NULL)
249                 {
250                     g->add_color(0.0f, color, 0.25f  + 0.5f * (1.0f - color.alpha()));
251                     g->add_color(1.0f, color, 1.0f);
252 
253                     s->parametric_bar(
254                         a, b, c, a2, b2, c2,
255                         cv->area_left(), cv->area_right(), cv->area_top(), cv->area_bottom(),
256                         g
257                     );
258                     s->parametric_line(a, b, c, cv->area_left(), cv->area_right(), cv->area_top(), cv->area_bottom(), l_width, col);
259 //                    s->parametric_line(a2, b2, c2, cv->area_left(), cv->area_right(), cv->area_top(), cv->area_bottom(), nWidth, col);
260 
261                     delete g;
262                 }
263             }
264             else {
265                 s->parametric_line(a, b, c, cv->area_left(), cv->area_right(), cv->area_top(), cv->area_bottom(), l_width, col);
266             }
267             s->set_antialiasing(aa);
268 
269             // Debug
270 //            if (nXFlags & F_EDITABLE)
271 //            {
272 //                float a1, b1, c1;
273 //                float a2, b2, c2;
274 //                float mx = nDMouseX, my = nDMouseY;
275 //                float nx, ny;
276 //
277 //                if (!parallel->parallel(x, y, a1, b1, c1))
278 //                    return;
279 //                if (!basis->parallel(mx, my, a2, b2, c2))
280 //                    return;
281 //                if (!line2d_intersection(a1, b1, c1, a2, b2, c2, nx, ny))
282 //                    return;
283 //
284 //                s->line(nx, ny, mx, my, 2, col);
285 //            }
286         }
287 
inside(ssize_t mx,ssize_t my)288         bool LSPMarker::inside(ssize_t mx, ssize_t my)
289         {
290             if (!(nXFlags & F_EDITABLE))
291                 return false;
292 
293             // Get graph
294             LSPGraph *cv = graph();
295             if (cv == NULL)
296                 return false;
297 
298             mx     -= cv->canvas_left();
299             my     -= cv->canvas_top();
300 
301 //            nDMouseX = mx; // DEBUG
302 //            nDMouseY = my; // DEBUG
303 //            query_draw(); // DEBUG
304 
305             // Get basis
306             LSPAxis *basis      = cv->axis(nBasisID);
307             if (basis == NULL)
308                 return false;
309             LSPAxis *parallel   = cv->axis(nParallelID);
310             if (parallel == NULL)
311                 return false;
312 
313             float x = 0.0f, y = 0.0f;
314             cv->center(nCenter, &x, &y);
315 
316             // Translate point and get the coordinates of point that lays on the target line
317             if (!basis->apply(&x, &y, &fValue, 1))
318                 return false;
319             if (fOffset != 0.0f)
320             {
321                 if (!parallel->apply(&x, &y, &fOffset, 1))
322                     return false;
323             }
324 
325             // Get equation of the line that contains calculated point
326             float a1, b1, c1;
327             float a2, b2, c2;
328             float nx, ny;
329 //            ssize_t border = (nBorder > 0) ? nBorder : -nBorder;
330 //            if (border > 3)
331 //                border
332 
333             if (!parallel->parallel(x, y, a1, b1, c1))
334                 return false;
335             if (!basis->parallel(mx, my, a2, b2, c2))
336                 return false;
337             if (!line2d_intersection(a1, b1, c1, a2, b2, c2, nx, ny))
338                 return false;
339 
340             return distance2d(nx, ny, mx, my) <= 3.0f;
341         }
342 
on_mouse_in(const ws_event_t * e)343         status_t LSPMarker::on_mouse_in(const ws_event_t *e)
344         {
345             nXFlags |= F_HIGHLIGHT;
346             query_draw();
347 
348             if (!(nXFlags & F_EDITABLE))
349                 return STATUS_OK;
350 
351             LSPGraph *cv = graph();
352             if (cv == NULL)
353                 return STATUS_OK;
354             LSPAxis *basis      = cv->axis(nBasisID);
355             if (basis == NULL)
356                 return STATUS_OK;
357 
358             float x = 0.0f, y = 0.0f;
359             if (!basis->apply(&x, &y, &fValue, 1))
360                 return STATUS_OK;
361 
362             if (fabs(x) > fabs(y))
363                 set_cursor(MP_HSIZE);
364             else
365                 set_cursor(MP_VSIZE);
366 
367             return LSPGraphItem::on_mouse_in(e);
368         }
369 
on_mouse_out(const ws_event_t * e)370         status_t LSPMarker::on_mouse_out(const ws_event_t *e)
371         {
372             nXFlags &= ~F_HIGHLIGHT;
373             query_draw();
374             lsp_trace("this = %p", this);
375             return STATUS_OK;
376         }
377 
on_mouse_down(const ws_event_t * e)378         status_t LSPMarker::on_mouse_down(const ws_event_t *e)
379         {
380             if (nMouseBtn == 0)
381             {
382                 if (!inside(e->nLeft, e->nTop))
383                     return STATUS_OK;
384 
385                 if ((e->nCode == MCB_LEFT) || (e->nCode == MCB_RIGHT))
386                 {
387                     nMouseX     = e->nLeft;
388                     nMouseY     = e->nTop;
389 
390 //                    LSPGraph *cv = graph();
391 //                    nDMouseX    = (cv != NULL) ? nMouseX - cv->canvas_left() : 0;
392 //                    nDMouseY    = (cv != NULL) ? nMouseY - cv->canvas_top() : 0;
393 //                    lsp_trace("dmouse = (%d, %d)", int(nDMouseX), int(nDMouseY));
394                     fLast       = fValue;
395                     nXFlags    |= F_EDITING;
396                     if (e->nCode == MCB_RIGHT)
397                         nXFlags    |= F_FINE_TUNE;
398                 }
399             }
400 
401             nMouseBtn  |= 1 << e->nCode;
402 
403             size_t bflag    = (nXFlags & F_FINE_TUNE) ? (1 << MCB_RIGHT) : (1 << MCB_LEFT);
404             if (nMouseBtn == bflag)
405                 apply_motion(e->nLeft, e->nTop);
406             else
407                 apply_motion(nMouseX, nMouseY);
408 
409             return STATUS_OK;
410         }
411 
on_mouse_up(const ws_event_t * e)412         status_t LSPMarker::on_mouse_up(const ws_event_t *e)
413         {
414             if ((!(nXFlags & F_EDITING)) || (nMouseBtn == 0))
415                 return STATUS_OK;
416 
417             size_t button   = 1 << e->nCode;
418             size_t bflag    = (nXFlags & F_FINE_TUNE) ? (1 << MCB_RIGHT) : (1 << MCB_LEFT);
419 
420             nMouseBtn      &= ~button;
421 
422             if (nMouseBtn != 0)
423             {
424                 if (nMouseBtn == bflag)
425                     apply_motion(e->nLeft, e->nTop);
426                 else
427                     apply_motion(nMouseX, nMouseY);
428             }
429             else
430             {
431                 if (button == bflag)
432                     apply_motion(e->nLeft, e->nTop);
433                 else
434                     apply_motion(nMouseX, nMouseY);
435 
436                 nXFlags    &= ~F_FINE_TUNE;
437             }
438 
439             return STATUS_OK;
440         }
441 
on_mouse_move(const ws_event_t * e)442         status_t LSPMarker::on_mouse_move(const ws_event_t *e)
443         {
444             if (nMouseBtn == 0)
445                 return STATUS_OK;
446 
447             size_t bflag    = (nXFlags & F_FINE_TUNE) ? (1 << MCB_RIGHT) : (1 << MCB_LEFT);
448 
449             if (nMouseBtn == bflag)
450                 apply_motion(e->nLeft, e->nTop);
451             else
452                 apply_motion(nMouseX, nMouseY);
453 
454             return STATUS_OK;
455         }
456 
apply_motion(ssize_t x,ssize_t y)457         void LSPMarker::apply_motion(ssize_t x, ssize_t y)
458         {
459             // Get graph
460             LSPGraph *cv = graph();
461             if (cv == NULL)
462                 return;
463 
464             // Ignore canvas coordinates
465 
466             // Get axises
467             LSPAxis *basis      = cv->axis(nBasisID);
468             if (basis == NULL)
469                 return;
470             LSPAxis *parallel   = cv->axis(nParallelID);
471             if (parallel == NULL)
472                 return;
473 
474             // Update the difference relative to the sensitivity
475             lsp_trace("xy=(%d, %d), mxy=(%d, %d)",
476                     int(x), int(y), int(nMouseX), int(nMouseY));
477 
478             float rx, ry;
479             if (nXFlags & F_FINE_TUNE)
480             {
481                 float dx = x - nMouseX, dy = y - nMouseY;
482                 rx      = nMouseX - cv->canvas_left() + 0.1f * dx;
483                 ry      = nMouseY - cv->canvas_top() + 0.1f * dy;
484             }
485             else
486             {
487                 rx      = x - cv->canvas_left();
488                 ry      = y - cv->canvas_top();
489             }
490 
491             lsp_trace("rxy=(%f, %f)", rx, ry);
492 
493             // Modify the value according to X coordinate
494             if ((rx != 0.0f) && (ry != 0.0f))
495                 lsp_trace("debug");
496 
497             float old       = fValue;
498             if ((nMouseX == x) && (nMouseY == y))
499                 fValue          = fLast;
500             else if (basis != NULL)
501                 fValue          = basis->project(rx, ry);
502             fValue          = limit_value(fValue);
503 
504             // Query widget for redraw
505             if (fValue != old)
506                 sSlots.execute(LSPSLOT_CHANGE, this);
507             query_draw();
508         }
509 
limit_value(float value)510         float LSPMarker::limit_value(float value)
511         {
512             if (fMin < fMax)
513             {
514                 if (value < fMin)
515                     value   = fMin;
516                 else if (value > fMax)
517                     value   = fMax;
518             }
519             else
520             {
521                 if (value < fMax)
522                     value   = fMax;
523                 else if (value > fMin)
524                     value   = fMin;
525             }
526             return value;
527         }
528     } /* namespace tk */
529 } /* namespace lsp */
530