1 /*
2 * Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
3 *
4 * This is free software: you can redistribute it and/or modify
5 * it under the terms of the Artistic License 2.0 as published by
6 * The Perl Foundation.
7 *
8 * This source is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * Artistic License 2.0 for more details.
12 *
13 * You should have received a copy of the Artistic License 2.0
14 * along the source as a COPYING file. If not, obtain it from
15 * http://www.perlfoundation.org/artistic_license_2_0.
16 */
17
18 #include <inttypes.h>
19
20 #include <sherlock.h>
21 #include <sherlock_nk.h>
22 #include <encoder.h>
23
24 typedef struct _midi_msg_t midi_msg_t;
25
26 struct _midi_msg_t {
27 uint8_t type;
28 const char *key;
29 };
30
31 #define COMMANDS_NUM 18
32 static const midi_msg_t commands [COMMANDS_NUM] = {
33 { LV2_MIDI_MSG_NOTE_OFF , "NoteOff" },
34 { LV2_MIDI_MSG_NOTE_ON , "NoteOn" },
35 { LV2_MIDI_MSG_NOTE_PRESSURE , "NotePressure" },
36 { LV2_MIDI_MSG_CONTROLLER , "Controller" },
37 { LV2_MIDI_MSG_PGM_CHANGE , "ProgramChange" },
38 { LV2_MIDI_MSG_CHANNEL_PRESSURE , "ChannelPressure" },
39 { LV2_MIDI_MSG_BENDER , "Bender" },
40 { LV2_MIDI_MSG_SYSTEM_EXCLUSIVE , "SystemExclusive" },
41 { LV2_MIDI_MSG_MTC_QUARTER , "QuarterFrame" },
42 { LV2_MIDI_MSG_SONG_POS , "SongPosition" },
43 { LV2_MIDI_MSG_SONG_SELECT , "SongSelect" },
44 { LV2_MIDI_MSG_TUNE_REQUEST , "TuneRequest" },
45 { LV2_MIDI_MSG_CLOCK , "Clock" },
46 { LV2_MIDI_MSG_START , "Start" },
47 { LV2_MIDI_MSG_CONTINUE , "Continue" },
48 { LV2_MIDI_MSG_STOP , "Stop" },
49 { LV2_MIDI_MSG_ACTIVE_SENSE , "ActiveSense" },
50 { LV2_MIDI_MSG_RESET , "Reset" },
51 };
52
53 #define CONTROLLERS_NUM 72
54 static const midi_msg_t controllers [CONTROLLERS_NUM] = {
55 { LV2_MIDI_CTL_MSB_BANK , "BankSelection_MSB" },
56 { LV2_MIDI_CTL_MSB_MODWHEEL , "Modulation_MSB" },
57 { LV2_MIDI_CTL_MSB_BREATH , "Breath_MSB" },
58 { LV2_MIDI_CTL_MSB_FOOT , "Foot_MSB" },
59 { LV2_MIDI_CTL_MSB_PORTAMENTO_TIME , "PortamentoTime_MSB" },
60 { LV2_MIDI_CTL_MSB_DATA_ENTRY , "DataEntry_MSB" },
61 { LV2_MIDI_CTL_MSB_MAIN_VOLUME , "MainVolume_MSB" },
62 { LV2_MIDI_CTL_MSB_BALANCE , "Balance_MSB" },
63 { LV2_MIDI_CTL_MSB_PAN , "Panpot_MSB" },
64 { LV2_MIDI_CTL_MSB_EXPRESSION , "Expression_MSB" },
65 { LV2_MIDI_CTL_MSB_EFFECT1 , "Effect1_MSB" },
66 { LV2_MIDI_CTL_MSB_EFFECT2 , "Effect2_MSB" },
67 { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE1 , "GeneralPurpose1_MSB" },
68 { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE2 , "GeneralPurpose2_MSB" },
69 { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE3 , "GeneralPurpose3_MSB" },
70 { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE4 , "GeneralPurpose4_MSB" },
71
72 { LV2_MIDI_CTL_LSB_BANK , "BankSelection_LSB" },
73 { LV2_MIDI_CTL_LSB_MODWHEEL , "Modulation_LSB" },
74 { LV2_MIDI_CTL_LSB_BREATH , "Breath_LSB" },
75 { LV2_MIDI_CTL_LSB_FOOT , "Foot_LSB" },
76 { LV2_MIDI_CTL_LSB_PORTAMENTO_TIME , "PortamentoTime_LSB" },
77 { LV2_MIDI_CTL_LSB_DATA_ENTRY , "DataEntry_LSB" },
78 { LV2_MIDI_CTL_LSB_MAIN_VOLUME , "MainVolume_LSB" },
79 { LV2_MIDI_CTL_LSB_BALANCE , "Balance_LSB" },
80 { LV2_MIDI_CTL_LSB_PAN , "Panpot_LSB" },
81 { LV2_MIDI_CTL_LSB_EXPRESSION , "Expression_LSB" },
82 { LV2_MIDI_CTL_LSB_EFFECT1 , "Effect1_LSB" },
83 { LV2_MIDI_CTL_LSB_EFFECT2 , "Effect2_LSB" },
84 { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE1 , "GeneralPurpose1_LSB" },
85 { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE2 , "GeneralPurpose2_LSB" },
86 { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE3 , "GeneralPurpose3_LSB" },
87 { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE4 , "GeneralPurpose4_LSB" },
88
89 { LV2_MIDI_CTL_SUSTAIN , "SustainPedal" },
90 { LV2_MIDI_CTL_PORTAMENTO , "Portamento" },
91 { LV2_MIDI_CTL_SOSTENUTO , "Sostenuto" },
92 { LV2_MIDI_CTL_SOFT_PEDAL , "SoftPedal" },
93 { LV2_MIDI_CTL_LEGATO_FOOTSWITCH , "LegatoFootSwitch" },
94 { LV2_MIDI_CTL_HOLD2 , "Hold2" },
95
96 { LV2_MIDI_CTL_SC1_SOUND_VARIATION , "SC1_SoundVariation" },
97 { LV2_MIDI_CTL_SC2_TIMBRE , "SC2_Timbre" },
98 { LV2_MIDI_CTL_SC3_RELEASE_TIME , "SC3_ReleaseTime" },
99 { LV2_MIDI_CTL_SC4_ATTACK_TIME , "SC4_AttackTime" },
100 { LV2_MIDI_CTL_SC5_BRIGHTNESS , "SC5_Brightness" },
101 { LV2_MIDI_CTL_SC6 , "SC6" },
102 { LV2_MIDI_CTL_SC7 , "SC7" },
103 { LV2_MIDI_CTL_SC8 , "SC8" },
104 { LV2_MIDI_CTL_SC9 , "SC9" },
105 { LV2_MIDI_CTL_SC10 , "SC10" },
106
107 { LV2_MIDI_CTL_GENERAL_PURPOSE5 , "GeneralPurpose5" },
108 { LV2_MIDI_CTL_GENERAL_PURPOSE6 , "GeneralPurpose6" },
109 { LV2_MIDI_CTL_GENERAL_PURPOSE7 , "GeneralPurpose7" },
110 { LV2_MIDI_CTL_GENERAL_PURPOSE8 , "GeneralPurpose8" },
111 { LV2_MIDI_CTL_PORTAMENTO_CONTROL , "PortamentoControl" },
112
113 { LV2_MIDI_CTL_E1_REVERB_DEPTH , "E1_ReverbDepth" },
114 { LV2_MIDI_CTL_E2_TREMOLO_DEPTH , "E2_TremoloDepth" },
115 { LV2_MIDI_CTL_E3_CHORUS_DEPTH , "E3_ChorusDepth" },
116 { LV2_MIDI_CTL_E4_DETUNE_DEPTH , "E4_DetuneDepth" },
117 { LV2_MIDI_CTL_E5_PHASER_DEPTH , "E5_PhaserDepth" },
118
119 { LV2_MIDI_CTL_DATA_INCREMENT , "DataIncrement" },
120 { LV2_MIDI_CTL_DATA_DECREMENT , "DataDecrement" },
121
122 { LV2_MIDI_CTL_NRPN_LSB , "NRPN_LSB" },
123 { LV2_MIDI_CTL_NRPN_MSB , "NRPN_MSB" },
124
125 { LV2_MIDI_CTL_RPN_LSB , "RPN_LSB" },
126 { LV2_MIDI_CTL_RPN_MSB , "RPN_MSB" },
127
128 { LV2_MIDI_CTL_ALL_SOUNDS_OFF , "AllSoundsOff" },
129 { LV2_MIDI_CTL_RESET_CONTROLLERS , "ResetControllers" },
130 { LV2_MIDI_CTL_LOCAL_CONTROL_SWITCH , "LocalControlSwitch" },
131 { LV2_MIDI_CTL_ALL_NOTES_OFF , "AllNotesOff" },
132 { LV2_MIDI_CTL_OMNI_OFF , "OmniOff" },
133 { LV2_MIDI_CTL_OMNI_ON , "OmniOn" },
134 { LV2_MIDI_CTL_MONO1 , "Mono1" },
135 { LV2_MIDI_CTL_MONO2 , "Mono2" },
136 };
137
138 #define TIMECODES_NUM 8
139 static const midi_msg_t timecodes [TIMECODES_NUM] = {
140 { 0 , "FrameNumber_LSB" },
141 { 1 , "FrameNumber_MSB" },
142 { 2 , "Second_LSB" },
143 { 3 , "Second_MSB" },
144 { 4 , "Minute_LSB" },
145 { 5 , "Minute_MSB" },
146 { 6 , "Hour_LSB" },
147 { 7 , "RateAndHour_MSB" },
148 };
149
150 static int
_cmp_search(const void * itm1,const void * itm2)151 _cmp_search(const void *itm1, const void *itm2)
152 {
153 const midi_msg_t *msg1 = itm1;
154 const midi_msg_t *msg2 = itm2;
155
156 if(msg1->type < msg2->type)
157 return -1;
158 else if(msg1->type > msg2->type)
159 return 1;
160
161 return 0;
162 }
163
164 static inline const midi_msg_t *
_search_command(uint8_t type)165 _search_command(uint8_t type)
166 {
167 return bsearch(&type, commands, COMMANDS_NUM, sizeof(midi_msg_t), _cmp_search);
168 }
169
170 static inline const midi_msg_t *
_search_controller(uint8_t type)171 _search_controller(uint8_t type)
172 {
173 return bsearch(&type, controllers, CONTROLLERS_NUM, sizeof(midi_msg_t), _cmp_search);
174 }
175
176 static inline const midi_msg_t *
_search_timecode(uint8_t type)177 _search_timecode(uint8_t type)
178 {
179 return bsearch(&type, timecodes, TIMECODES_NUM, sizeof(midi_msg_t), _cmp_search);
180 }
181
182 static const char *keys [12] = {
183 "C", "C#",
184 "D", "D#",
185 "E",
186 "F", "F#",
187 "G", "G#",
188 "A", "A#",
189 "B"
190 };
191
192 static inline const char *
_note(uint8_t val,int8_t * octave)193 _note(uint8_t val, int8_t *octave)
194 {
195 *octave = val / 12 - 1;
196
197 return keys[val % 12];
198 }
199
200 static inline void
_shadow(struct nk_context * ctx,bool * shadow)201 _shadow(struct nk_context *ctx, bool *shadow)
202 {
203 if(*shadow)
204 {
205 struct nk_style *style = &ctx->style;
206 const struct nk_vec2 group_padding = style->window.group_padding;
207 struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
208
209 struct nk_rect b = nk_widget_bounds(ctx);
210 b.x -= group_padding.x;
211 b.w *= 10;
212 b.w += 5*group_padding.x;
213 nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
214 }
215
216 *shadow = !*shadow;
217 }
218
219 void
_midi_inspector_expose(struct nk_context * ctx,struct nk_rect wbounds,void * data)220 _midi_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
221 {
222 plughandle_t *handle = data;
223
224 handle->dy = 20.f * _get_scale(handle);
225 const float widget_h = handle->dy;
226 struct nk_style *style = &ctx->style;
227 const struct nk_vec2 window_padding = style->window.padding;
228 const struct nk_vec2 group_padding = style->window.group_padding;
229
230 const char *window_name = "Sherlock";
231 if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
232 {
233 struct nk_panel *panel = nk_window_get_panel(ctx);
234 struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
235
236 const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h;
237 nk_layout_row_dynamic(ctx, body_h, 1);
238 nk_flags flags = NK_WINDOW_BORDER;
239 if(handle->state.follow)
240 {
241 flags |= NK_WINDOW_NO_SCROLLBAR;
242 }
243 else
244 {
245 handle->shadow = false;
246 }
247 struct nk_list_view lview;
248 if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
249 {
250 if(handle->state.follow)
251 {
252 lview.end = NK_MAX(handle->n_item, 0);
253 lview.begin = NK_MAX(lview.end - lview.count, 0);
254 }
255 for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
256 {
257 item_t *itm = handle->items[l];
258
259 switch(itm->type)
260 {
261 case ITEM_TYPE_NONE:
262 {
263 // skip, was sysex payload
264 } break;
265 case ITEM_TYPE_FRAME:
266 {
267 nk_layout_row_dynamic(ctx, widget_h, 3);
268 {
269 struct nk_rect b = nk_widget_bounds(ctx);
270 b.x -= group_padding.x;
271 b.w *= 3;
272 b.w += 4*group_padding.x;
273 nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
274 }
275
276 nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
277 nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
278 nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
279
280 handle->shadow = false;
281 } break;
282
283 case ITEM_TYPE_EVENT:
284 {
285 LV2_Atom_Event *ev = &itm->event.ev;
286 const LV2_Atom *body = &ev->body;
287 const int64_t frames = ev->time.frames;
288 const uint8_t *msg = LV2_ATOM_BODY_CONST(body);
289 const uint8_t cmd = (msg[0] & 0xf0) == 0xf0
290 ? msg[0]
291 : msg[0] & 0xf0;
292
293 const midi_msg_t *command_msg = _search_command(cmd);
294 const char *command_str = command_msg
295 ? command_msg->key
296 : "Unknown";
297
298 char tmp [16];
299 nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7);
300 {
301 nk_layout_row_push(ctx, 0.1);
302 _shadow(ctx, &handle->shadow);
303 nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
304
305 nk_layout_row_push(ctx, 0.2);
306 const unsigned rem = body->size;
307 const unsigned to = rem >= 4 ? 4 : rem % 4;
308 for(unsigned i=0, ptr=0; i<to; i++, ptr+=3)
309 sprintf(&tmp[ptr], "%02"PRIX8" ", msg[i]);
310 tmp[to*3 - 1] = '\0';
311 nk_label_colored(ctx, tmp, NK_TEXT_LEFT, cwhite);
312
313 nk_layout_row_push(ctx, 0.2);
314 nk_label_colored(ctx, command_str, NK_TEXT_LEFT, magenta);
315
316 switch(cmd)
317 {
318 case LV2_MIDI_MSG_NOTE_OFF:
319 // fall-through
320 case LV2_MIDI_MSG_NOTE_ON:
321 // fall-through
322 case LV2_MIDI_MSG_NOTE_PRESSURE:
323 {
324 nk_layout_row_push(ctx, 0.1);
325 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
326 (msg[0] & 0x0f) + 1);
327
328 nk_layout_row_push(ctx, 0.2);
329 int8_t octave;
330 const char *key = _note(msg[1], &octave);
331 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%s%+"PRIi8"=%3"PRIu8,
332 key, octave, msg[1]);
333
334 nk_layout_row_push(ctx, 0.1);
335 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[2]);
336 } break;
337 case LV2_MIDI_MSG_CONTROLLER:
338 {
339 nk_layout_row_push(ctx, 0.1);
340 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
341 (msg[0] & 0x0f) + 1);
342
343 const midi_msg_t *controller_msg = _search_controller(msg[1]);
344 const char *controller_str = controller_msg
345 ? controller_msg->key
346 : "Unknown";
347 nk_layout_row_push(ctx, 0.2);
348 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%s=%3"PRIu8"",
349 controller_str, msg[1]);
350
351 nk_layout_row_push(ctx, 0.1);
352 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[2]);
353 } break;
354 case LV2_MIDI_MSG_PGM_CHANGE:
355 // fall-through
356 case LV2_MIDI_MSG_CHANNEL_PRESSURE:
357 {
358 nk_layout_row_push(ctx, 0.1);
359 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
360 (msg[0] & 0x0f) + 1);
361
362 nk_layout_row_push(ctx, 0.2);
363 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[1]);
364
365 nk_layout_row_push(ctx, 0.1);
366 _empty(ctx);
367 } break;
368 case LV2_MIDI_MSG_BENDER:
369 {
370 const int16_t bender = (((int16_t)msg[2] << 7) | msg[1]) - 0x2000;
371
372 nk_layout_row_push(ctx, 0.1);
373 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8,
374 (msg[0] & 0x0f) + 1);
375
376 nk_layout_row_push(ctx, 0.2);
377 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIi16, bender);
378
379 nk_layout_row_push(ctx, 0.1);
380 _empty(ctx);
381 } break;
382 case LV2_MIDI_MSG_MTC_QUARTER:
383 {
384 const uint8_t msg_type = msg[1] >> 4;
385 const uint8_t msg_val = msg[1] & 0xf;
386
387 nk_layout_row_push(ctx, 0.1);
388 _empty(ctx);
389
390 const midi_msg_t *timecode_msg = _search_timecode(msg_type);
391 const char *timecode_str = timecode_msg
392 ? timecode_msg->key
393 : "Unknown";
394 nk_layout_row_push(ctx, 0.2);
395 nk_label_colored(ctx, timecode_str, NK_TEXT_RIGHT, cwhite);
396
397 nk_layout_row_push(ctx, 0.1);
398 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg_val);
399 } break;
400 case LV2_MIDI_MSG_SONG_POS:
401 {
402 const int16_t song_pos= (((int16_t)msg[2] << 7) | msg[1]);
403
404 nk_layout_row_push(ctx, 0.1);
405 _empty(ctx);
406
407 nk_layout_row_push(ctx, 0.2);
408 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu16, song_pos);
409
410 nk_layout_row_push(ctx, 0.1);
411 _empty(ctx);
412 } break;
413 case LV2_MIDI_MSG_SONG_SELECT:
414 {
415 nk_layout_row_push(ctx, 0.1);
416 _empty(ctx);
417
418 nk_layout_row_push(ctx, 0.2);
419 nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[1]);
420
421 nk_layout_row_push(ctx, 0.1);
422 _empty(ctx);
423 } break;
424 case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE:
425 // fall-throuh
426 case LV2_MIDI_MSG_TUNE_REQUEST:
427 // fall-throuh
428 case LV2_MIDI_MSG_CLOCK:
429 // fall-throuh
430 case LV2_MIDI_MSG_START:
431 // fall-throuh
432 case LV2_MIDI_MSG_CONTINUE:
433 // fall-throuh
434 case LV2_MIDI_MSG_STOP:
435 // fall-throuh
436 case LV2_MIDI_MSG_ACTIVE_SENSE:
437 // fall-throuh
438 case LV2_MIDI_MSG_RESET:
439 {
440 nk_layout_row_push(ctx, 0.1);
441 _empty(ctx);
442
443 nk_layout_row_push(ctx, 0.2);
444 _empty(ctx);
445
446 nk_layout_row_push(ctx, 0.1);
447 _empty(ctx);
448 } break;
449 }
450
451 nk_layout_row_push(ctx, 0.1);
452 nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size);
453 }
454 nk_layout_row_end(ctx);
455
456 for(unsigned j=4; j<body->size; j+=4)
457 {
458 nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7);
459 {
460 nk_layout_row_push(ctx, 0.1);
461 _shadow(ctx, &handle->shadow);
462 _empty(ctx);
463
464 nk_layout_row_push(ctx, 0.2);
465 const unsigned rem = body->size - j;
466 const unsigned to = rem >= 4 ? 4 : rem % 4;
467 for(unsigned i=0, ptr=0; i<to; i++, ptr+=3)
468 sprintf(&tmp[ptr], "%02"PRIX8" ", msg[j+i]);
469 tmp[to*3 - 1] = '\0';
470 nk_label_colored(ctx, tmp, NK_TEXT_LEFT, cwhite);
471
472 nk_layout_row_push(ctx, 0.2);
473 _empty(ctx);
474
475 nk_layout_row_push(ctx, 0.1);
476 _empty(ctx);
477
478 nk_layout_row_push(ctx, 0.2);
479 _empty(ctx);
480
481 nk_layout_row_push(ctx, 0.1);
482 _empty(ctx);
483
484 nk_layout_row_push(ctx, 0.1);
485 _empty(ctx);
486 }
487 }
488 } break;
489 }
490 }
491
492 nk_list_view_end(&lview);
493 }
494
495 const float n = 3;
496 const float r0 = 1.f / n;
497 const float r1 = 0.1f / 3;
498 const float r2 = r0 - r1;
499 const float footer [6] = {r1, r2, r1, r2, r1, r2};
500 nk_layout_row(ctx, NK_DYNAMIC, widget_h, 6, footer);
501 {
502 const int32_t state_overwrite = _check(ctx, handle->state.overwrite);
503 if(state_overwrite != handle->state.overwrite)
504 {
505 handle->state.overwrite = state_overwrite;
506 _set_bool(handle, handle->urid.overwrite, handle->state.overwrite);
507 }
508 nk_label(ctx, "overwrite", NK_TEXT_LEFT);
509
510 const int32_t state_block = _check(ctx, handle->state.block);
511 if(state_block != handle->state.block)
512 {
513 handle->state.block = state_block;
514 _set_bool(handle, handle->urid.block, handle->state.block);
515 }
516 nk_label(ctx, "block", NK_TEXT_LEFT);
517
518 const int32_t state_follow = _check(ctx, handle->state.follow);
519 if(state_follow != handle->state.follow)
520 {
521 handle->state.follow = state_follow;
522 _set_bool(handle, handle->urid.follow, handle->state.follow);
523 }
524 nk_label(ctx, "follow", NK_TEXT_LEFT);
525 }
526
527 const bool max_reached = handle->n_item >= MAX_LINES;
528 nk_layout_row_dynamic(ctx, widget_h, 2);
529 if(nk_button_symbol_label(ctx,
530 max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
531 "clear", NK_TEXT_LEFT))
532 {
533 _clear(handle);
534 }
535 nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
536 }
537 nk_end(ctx);
538 }
539