Solvespace

Форк
0
/
textwin.cpp 
1170 строк · 39.0 Кб
1
//-----------------------------------------------------------------------------
2
// Helper functions for the text-based browser window.
3
//
4
// Copyright 2008-2013 Jonathan Westhues.
5
//-----------------------------------------------------------------------------
6
#include "solvespace.h"
7

8
namespace SolveSpace {
9

10
class Button {
11
public:
12
    virtual std::string Tooltip() = 0;
13
    virtual void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) = 0;
14
    virtual int AdvanceWidth() = 0;
15
    virtual void Click() = 0;
16
};
17

18
class SpacerButton : public Button {
19
public:
20
    std::string Tooltip() override { return ""; }
21

22
    void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override {
23
        // Draw a darker-grey spacer in between the groups of icons.
24
        uiCanvas->DrawRect(x, x + 4, y, y - 24,
25
                           /*fillColor=*/{ 45, 45, 45, 255 },
26
                           /*outlineColor=*/{});
27
    }
28

29
    int AdvanceWidth() override { return 12; }
30

31
    void Click() override {}
32
};
33

34
class ShowHideButton : public Button {
35
public:
36
    bool        *variable;
37
    std::string tooltip;
38
    std::string iconName;
39
    std::shared_ptr<Pixmap> icon;
40

41
    ShowHideButton(bool *variable, std::string iconName, std::string tooltip)
42
            : variable(variable), tooltip(tooltip), iconName(iconName) {}
43

44
    std::string Tooltip() override {
45
        return ((*variable) ? "Hide " : "Show ") + tooltip;
46
    }
47

48
    void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override {
49
        if(icon == NULL) {
50
            icon = LoadPng("icons/text-window/" + iconName + ".png");
51
        }
52

53
        uiCanvas->DrawPixmap(icon, x, y - 24);
54
        if(asHovered) {
55
            uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26,
56
                               /*fillColor=*/{ 255, 255, 0, 75 },
57
                               /*outlineColor=*/{});
58
        }
59
        if(!*(variable)) {
60
            int s = 0, f = 23;
61
            RgbaColor color = { 255, 0, 0, 150 };
62
            uiCanvas->DrawLine(x+s, y-s, x+f, y-f, color, 2);
63
            uiCanvas->DrawLine(x+s, y-f, x+f, y-s, color, 2);
64
        }
65
    }
66

67
    int AdvanceWidth() override { return 32; }
68

69
    void Click() override { SS.GW.ToggleBool(variable); }
70
};
71

72
class FacesButton : public ShowHideButton {
73
public:
74
    FacesButton()
75
        : ShowHideButton(&(SS.GW.showFaces), "faces", "") {}
76

77
    std::string Tooltip() override {
78
        if(*variable) {
79
            return "Don't make faces selectable with mouse";
80
        } else {
81
            return "Make faces selectable with mouse";
82
        }
83
    }
84
};
85

86
#include <array>
87
class TriStateButton : public Button {
88
public:
89
    static const size_t tri = 3;
90

91
    TriStateButton(unsigned *variable, const std::array<unsigned, tri> &states,
92
                   const std::array<std::string, tri> &tooltips,
93
                   const std::array<std::string, tri> &iconNames)
94
        : variable(variable), states(states), tooltips(tooltips), iconNames(iconNames) {
95
    }
96

97
    unsigned *const variable;
98
    const std::array<unsigned, tri> states;
99
    const std::array<std::string, tri> tooltips;
100
    const std::array<std::string, tri> iconNames;
101
    std::shared_ptr<Pixmap> icons[tri];
102

103
    std::string Tooltip() override {
104
        for(size_t k = 0; k < tri; ++k)
105
            if(*variable == states[k])
106
                return tooltips[k];
107
        ssassert(false, "Unexpected mode");
108
    }
109

110
    void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override {
111
        for(size_t k = 0; k < tri; ++k)
112
            if(icons[k] == nullptr)
113
                icons[k] = LoadPng("icons/text-window/" + iconNames[k] + ".png");
114

115
        std::shared_ptr<Pixmap> icon;
116
        for(size_t k = 0; k < tri; ++k)
117
            if(*variable == states[k]) {
118
                icon = icons[k];
119
                break;
120
            }
121

122
        uiCanvas->DrawPixmap(icon, x, y - 24);
123
        if(asHovered) {
124
            uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26,
125
                               /*fillColor=*/{255, 255, 0, 75},
126
                               /*outlineColor=*/{});
127
        }
128
    }
129

130

131
    int AdvanceWidth() override { return 32; }
132

133
    void Click() override {
134
        for(size_t k = 0; k < tri; ++k)
135
            if(*variable == states[k]) {
136
                *variable = states[(k + 1) % tri];
137
                break;
138
            }
139

140
        SS.GenerateAll();
141
        SS.GW.Invalidate();
142
        SS.ScheduleShowTW();
143
    }
144
};
145

146
static SpacerButton   spacerButton;
147

148
static ShowHideButton workplanesButton =
149
    { &(SS.GW.showWorkplanes),   "workplane",     "workplanes from inactive groups" };
150
static ShowHideButton normalsButton =
151
    { &(SS.GW.showNormals),      "normal",        "normals"                         };
152
static ShowHideButton pointsButton =
153
    { &(SS.GW.showPoints),       "point",         "points"                          };
154
static ShowHideButton constructionButton =
155
    { &(SS.GW.showConstruction), "construction",  "construction entities"           };
156
static TriStateButton constraintsButton  = {
157
    (unsigned *)(&(SS.GW.showConstraints)),
158
    {(unsigned)GraphicsWindow::ShowConstraintMode::SCM_SHOW_ALL,
159
     (unsigned)GraphicsWindow::ShowConstraintMode::SCM_SHOW_DIM,
160
     (unsigned)GraphicsWindow::ShowConstraintMode::SCM_NOSHOW},
161
    {"Show only dimensions", "Hide constraints and dimensions", "Show constraints and dimensions"},
162
    {"constraint", "constraint-dimo", "constraint-wo"}};
163
static FacesButton facesButton;
164
static ShowHideButton shadedButton =
165
    { &(SS.GW.showShaded),       "shaded",        "shaded view of solid model"      };
166
static ShowHideButton edgesButton =
167
    { &(SS.GW.showEdges),        "edges",         "edges of solid model"            };
168
static ShowHideButton outlinesButton =
169
    { &(SS.GW.showOutlines),     "outlines",      "outline of solid model"          };
170
static ShowHideButton meshButton =
171
    { &(SS.GW.showMesh),         "mesh",          "triangle mesh of solid model"    };
172
static TriStateButton occludedLinesButton = {
173
    (unsigned *)(&(SS.GW.drawOccludedAs)),
174
    {(unsigned)GraphicsWindow::DrawOccludedAs::INVISIBLE,
175
     (unsigned)GraphicsWindow::DrawOccludedAs::STIPPLED,
176
     (unsigned)GraphicsWindow::DrawOccludedAs::VISIBLE},
177
    {"Stipple occluded lines", "Draw occluded lines", "Don't draw occluded lines"},
178
    {"occluded-invisible", "occluded-stippled", "occluded-visible"}};
179

180
static Button *buttons[] = {
181
    &workplanesButton,
182
    &normalsButton,
183
    &pointsButton,
184
    &constructionButton,
185
    &constraintsButton,
186
    &facesButton,
187
    &spacerButton,
188
    &shadedButton,
189
    &edgesButton,
190
    &outlinesButton,
191
    &meshButton,
192
    &spacerButton,
193
    &occludedLinesButton,
194
};
195

196
/** Foreground color codes. */
197
const TextWindow::Color TextWindow::fgColors[] = {
198
    { 'd', RGBi(255, 255, 255) },  // Default   : white
199
    { 'l', RGBi(100, 200, 255) },  // links     : blue
200
    { 't', RGBi(255, 200, 100) },  // tree/text : yellow
201
    { 'h', RGBi( 90,  90,  90) },
202
    { 's', RGBi( 40, 255,  40) },  // Ok        : green
203
    { 'm', RGBi(200, 200,   0) },
204
    { 'r', RGBi(  0,   0,   0) },  // Reverse   : black
205
    { 'x', RGBi(255,  20,  20) },  // Error     : red
206
    { 'i', RGBi(  0, 255, 255) },  // Info      : cyan
207
    { 'g', RGBi(128, 128, 128) },  // Disabled  : gray
208
    { 'b', RGBi(200, 200, 200) },
209
    { 0,   RGBi(  0,   0,   0) }
210
};
211
/** Background color codes. */
212
const TextWindow::Color TextWindow::bgColors[] = {
213
    { 'd', RGBi(  0,   0,   0) },  // Default   : black
214
    { 't', RGBi( 34,  15,  15) },
215
    { 'a', RGBi( 25,  25,  25) },  // Alternate : dark gray
216
    { 'r', RGBi(255, 255, 255) },  // Reverse   : white
217
    { 0,   RGBi(  0,   0,   0) }
218
};
219

220
void TextWindow::MakeColorTable(const Color *in, float *out) {
221
    int i;
222
    for(i = 0; in[i].c != 0; i++) {
223
        int c = in[i].c;
224
        ssassert(c >= 0 && c <= 255, "Unexpected color index");
225
        out[c*3 + 0] = in[i].color.redF();
226
        out[c*3 + 1] = in[i].color.greenF();
227
        out[c*3 + 2] = in[i].color.blueF();
228
    }
229
}
230

231
void TextWindow::Init() {
232
    if(!window) {
233
        window = Platform::CreateWindow(Platform::Window::Kind::TOOL, SS.GW.window);
234
        if(window) {
235
            canvas = CreateRenderer();
236

237
            using namespace std::placeholders;
238
            window->onClose = []() {
239
                SS.TW.HideEditControl();
240
                SS.GW.showTextWindow = false;
241
                SS.GW.EnsureValidActives();
242
            };
243
            window->onMouseEvent = [this](Platform::MouseEvent event) {
244
                using Platform::MouseEvent;
245

246
                if(event.type == MouseEvent::Type::PRESS ||
247
                   event.type == MouseEvent::Type::DBL_PRESS ||
248
                   event.type == MouseEvent::Type::MOTION) {
249
                    bool isClick  = (event.type != MouseEvent::Type::MOTION);
250
                    bool leftDown = (event.button == MouseEvent::Button::LEFT);
251
                    this->MouseEvent(isClick, leftDown, event.x, event.y);
252
                    return true;
253
                } else if(event.type == MouseEvent::Type::LEAVE) {
254
                    MouseLeave();
255
                    return true;
256
                } else if(event.type == MouseEvent::Type::SCROLL_VERT) {
257
                    if (event.scrollDelta == 0) {
258
                        return true;
259
                    }
260
                    if (abs(event.scrollDelta) < 0.2) {
261
                        if (event.scrollDelta > 0) {
262
                            event.scrollDelta = 0.2;
263
                        } else {
264
                            event.scrollDelta = -0.2;
265
                        }
266
                    }
267
                    double offset = LINE_HEIGHT / 2 * event.scrollDelta;
268
                    ScrollbarEvent(window->GetScrollbarPosition() - offset);
269
                }
270
                return false;
271
            };
272
            window->onKeyboardEvent = SS.GW.window->onKeyboardEvent;
273
            window->onRender = std::bind(&TextWindow::Paint, this);
274
            window->onEditingDone = std::bind(&TextWindow::EditControlDone, this, _1);
275
            window->onScrollbarAdjusted = std::bind(&TextWindow::ScrollbarEvent, this, _1);
276
            window->SetMinContentSize(370, 370);
277
        }
278
    }
279

280
    ClearSuper();
281
}
282

283
void TextWindow::ClearSuper() {
284
    // Ugly hack, but not so ugly as the next line
285
    Platform::WindowRef oldWindow = std::move(window);
286
    std::shared_ptr<ViewportCanvas> oldCanvas = canvas;
287

288
    // Cannot use *this = {} here because TextWindow instances
289
    // are 2.4MB long; this causes stack overflows in prologue
290
    // when built with MSVC, even with optimizations.
291
    memset(this, 0, sizeof(*this));
292

293
    // Return old canvas
294
    window = std::move(oldWindow);
295
    canvas = oldCanvas;
296

297
    HideEditControl();
298

299
    MakeColorTable(fgColors, fgColorTable);
300
    MakeColorTable(bgColors, bgColorTable);
301

302
    ClearScreen();
303
    Show();
304
}
305

306
void TextWindow::HideEditControl() {
307
    editControl.colorPicker.show = false;
308
    if(window) {
309
        window->HideEditor();
310
        window->Invalidate();
311
    }
312
}
313

314
void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) {
315
    if(halfRow < 0) halfRow = top[hoveredRow];
316
    editControl.halfRow = halfRow;
317
    editControl.col = col;
318

319
    int x = LEFT_MARGIN + CHAR_WIDTH_*col;
320
    int y = (halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);
321

322
    double width, height;
323
    window->GetContentSize(&width, &height);
324
    window->ShowEditor(x, y + LINE_HEIGHT - 2, LINE_HEIGHT - 4,
325
                       width - x, /*isMonospace=*/true, str);
326
}
327

328
void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb) {
329
    SS.ScheduleShowTW();
330

331
    editControl.colorPicker.show = true;
332
    editControl.colorPicker.rgb = rgb;
333
    editControl.colorPicker.h = 0;
334
    editControl.colorPicker.s = 0;
335
    editControl.colorPicker.v = 1;
336
    ShowEditControl(col, ssprintf("%.2f, %.2f, %.2f", rgb.redF(), rgb.greenF(), rgb.blueF()));
337
}
338

339
void TextWindow::ClearScreen() {
340
    int i, j;
341
    for(i = 0; i < MAX_ROWS; i++) {
342
        for(j = 0; j < MAX_COLS; j++) {
343
            text[i][j] = ' ';
344
            meta[i][j].fg = 'd';
345
            meta[i][j].bg = 'd';
346
            meta[i][j].link = NOT_A_LINK;
347
        }
348
        top[i] = i*2;
349
    }
350
    rows = 0;
351
}
352

353
// This message was added when someone had too many fonts for the text window
354
// Scrolling seemed to be broken, but was actually at the MAX_ROWS.
355
static const char* endString = "    **** End of Text Screen ****";
356

357
void TextWindow::Printf(bool halfLine, const char *fmt, ...) {
358
    if(!canvas) return;
359

360
    if(rows >= MAX_ROWS) return;
361

362
    if(rows >= MAX_ROWS-2 && (fmt != endString)) {
363
        // twice due to some half-row issues on resizing
364
        Printf(halfLine, endString);
365
        Printf(halfLine, endString);
366
        return;
367
    }
368
    
369
    va_list vl;
370
    va_start(vl, fmt);
371

372
    int r, c;
373
    r = rows;
374
    top[r] = (r == 0) ? 0 : (top[r-1] + (halfLine ? 3 : 2));
375
    rows++;
376

377
    for(c = 0; c < MAX_COLS; c++) {
378
        text[r][c] = ' ';
379
        meta[r][c].link = NOT_A_LINK;
380
    }
381

382
    char fg = 'd';
383
    char bg = 'd';
384
    RgbaColor bgRgb = RGBi(0, 0, 0);
385
    int link = NOT_A_LINK;
386
    uint32_t data = 0;
387
    LinkFunction *f = NULL, *h = NULL;
388

389
    c = 0;
390
    while(*fmt) {
391
        char buf[1024];
392

393
        if(*fmt == '%') {
394
            fmt++;
395
            if(*fmt == '\0') goto done;
396
            strcpy(buf, "");
397
            switch(*fmt) {
398
                case 'd': {
399
                    int v = va_arg(vl, int);
400
                    sprintf(buf, "%d", v);
401
                    break;
402
                }
403
                case 'x': {
404
                    unsigned int v = va_arg(vl, unsigned int);
405
                    sprintf(buf, "%08x", v);
406
                    break;
407
                }
408
                case '@': {
409
                    double v = va_arg(vl, double);
410
                    sprintf(buf, "%.2f", v);
411
                    break;
412
                }
413
                case '2': {
414
                    double v = va_arg(vl, double);
415
                    sprintf(buf, "%s%.2f", v < 0 ? "" : " ", v);
416
                    break;
417
                }
418
                case '3': {
419
                    double v = va_arg(vl, double);
420
                    sprintf(buf, "%s%.3f", v < 0 ? "" : " ", v);
421
                    break;
422
                }
423
                case '#': {
424
                    double v = va_arg(vl, double);
425
                    sprintf(buf, "%.3f", v);
426
                    break;
427
                }
428
                case 's': {
429
                    char *s = va_arg(vl, char *);
430
                    memcpy(buf, s, min(sizeof(buf), strlen(s)+1));
431
                    break;
432
                }
433
                case 'c': {
434
                    // 'char' is promoted to 'int' when passed through '...'
435
                    int v = va_arg(vl, int);
436
                    if(v == 0) {
437
                        strcpy(buf, "");
438
                    } else {
439
                        sprintf(buf, "%c", v);
440
                    }
441
                    break;
442
                }
443
                case 'E':
444
                    fg = 'd';
445
                    // leave the background, though
446
                    link = NOT_A_LINK;
447
                    data = 0;
448
                    f = NULL;
449
                    h = NULL;
450
                    break;
451

452
                case 'F':
453
                case 'B': {
454
                    char cc = fmt[1];  // color code
455
                    RgbaColor *rgbPtr = NULL;
456
                    switch(cc) {
457
                        case 0:   goto done;  // truncated directive
458
                        case 'p': cc = (char)va_arg(vl, int); break;
459
                        case 'z': rgbPtr = va_arg(vl, RgbaColor *); break;
460
                    }
461
                    if(*fmt == 'F') {
462
                        fg = cc;
463
                    } else {
464
                        bg = cc;
465
                        if(rgbPtr) bgRgb = *rgbPtr;
466
                    }
467
                    fmt++;
468
                    break;
469
                }
470
                case 'L':
471
                    if(fmt[1] == '\0') goto done;
472
                    fmt++;
473
                    if(*fmt == 'p') {
474
                        link = va_arg(vl, int);
475
                    } else {
476
                        link = *fmt;
477
                    }
478
                    break;
479

480
                case 'f':
481
                    f = va_arg(vl, LinkFunction *);
482
                    break;
483

484
                case 'h':
485
                    h = va_arg(vl, LinkFunction *);
486
                    break;
487

488
                case 'D': {
489
                    unsigned int v = va_arg(vl, unsigned int);
490
                    data = (uint32_t)v;
491
                    break;
492
                }
493
                case '%':
494
                    strcpy(buf, "%");
495
                    break;
496
            }
497
        } else {
498
            utf8_iterator it2(fmt), it1 = it2++;
499
            strncpy(buf, fmt, it2 - it1);
500
            buf[it2 - it1] = '\0';
501
        }
502

503
        for(utf8_iterator it(buf); *it; ++it) {
504
            for(size_t i = 0; i < canvas->GetBitmapFont()->GetWidth(*it); i++) {
505
                if(c >= MAX_COLS) goto done;
506
                text[r][c] = (i == 0) ? *it : ' ';
507
                meta[r][c].fg = fg;
508
                meta[r][c].bg = bg;
509
                meta[r][c].bgRgb = bgRgb;
510
                meta[r][c].link = link;
511
                meta[r][c].data = data;
512
                meta[r][c].f = f;
513
                meta[r][c].h = h;
514
                c++;
515
            }
516
        }
517

518
        utf8_iterator it(fmt);
519
        it++;
520
        fmt = it.ptr();
521
    }
522
    while(c < MAX_COLS) {
523
        meta[r][c].fg = fg;
524
        meta[r][c].bg = bg;
525
        meta[r][c].bgRgb = bgRgb;
526
        c++;
527
    }
528

529
done:
530
    va_end(vl);
531
}
532

533
void TextWindow::Show() {
534
    if(SS.GW.pending.operation == GraphicsWindow::Pending::NONE) {
535
        SS.GW.ClearPending(/*scheduleShowTW=*/false);
536
    }
537

538
    SS.GW.GroupSelection();
539
    auto const &gs = SS.GW.gs;
540

541
    // Make sure these tests agree with test used to draw indicator line on
542
    // main list of groups screen.
543
    if(SS.GW.pending.description) {
544
        // A pending operation (that must be completed with the mouse in
545
        // the graphics window) will preempt our usual display.
546
        HideEditControl();
547
        ShowHeader(false);
548
        Printf(false, "");
549
        Printf(false, "%s", SS.GW.pending.description);
550
        Printf(true, "%Fl%f%Ll(cancel operation)%E",
551
            &TextWindow::ScreenUnselectAll);
552
    } else if((gs.n > 0 || gs.constraints > 0) &&
553
                                    shown.screen != Screen::PASTE_TRANSFORMED)
554
    {
555
        if(edit.meaning != Edit::TTF_TEXT) HideEditControl();
556
        ShowHeader(false);
557
        DescribeSelection();
558
    } else {
559
        if(edit.meaning == Edit::TTF_TEXT) HideEditControl();
560
        ShowHeader(true);
561
        switch(shown.screen) {
562
            default:
563
                shown.screen = Screen::LIST_OF_GROUPS;
564
                // fall through
565
            case Screen::LIST_OF_GROUPS:     ShowListOfGroups();     break;
566
            case Screen::GROUP_INFO:         ShowGroupInfo();        break;
567
            case Screen::GROUP_SOLVE_INFO:   ShowGroupSolveInfo();   break;
568
            case Screen::CONFIGURATION:      ShowConfiguration();    break;
569
            case Screen::STEP_DIMENSION:     ShowStepDimension();    break;
570
            case Screen::LIST_OF_STYLES:     ShowListOfStyles();     break;
571
            case Screen::STYLE_INFO:         ShowStyleInfo();        break;
572
            case Screen::PASTE_TRANSFORMED:  ShowPasteTransformed(); break;
573
            case Screen::EDIT_VIEW:          ShowEditView();         break;
574
            case Screen::TANGENT_ARC:        ShowTangentArc();       break;
575
        }
576
    }
577
    Printf(false, "");
578

579
    // Make sure there's room for the color picker
580
    if(editControl.colorPicker.show) {
581
        int pickerHeight = 25;
582
        int halfRow = editControl.halfRow;
583
        if(top[rows-1] - halfRow < pickerHeight && rows < MAX_ROWS) {
584
            rows++;
585
            top[rows-1] = halfRow + pickerHeight;
586
        }
587
    }
588

589
    if(window) Resize();
590
}
591

592
void TextWindow::Resize()
593
{
594
    double width, height;
595
    window->GetContentSize(&width, &height);
596

597
    halfRows = (int)height / (LINE_HEIGHT/2);
598

599
    int bottom = top[rows-1] + 2;
600
    scrollPos = min(scrollPos, bottom - halfRows);
601
    scrollPos = max(scrollPos, 0);
602

603
    window->ConfigureScrollbar(0, top[rows - 1] + 1, halfRows);
604
    window->SetScrollbarPosition(scrollPos);
605
    window->SetScrollbarVisible(top[rows - 1] + 1 > halfRows);
606
    window->Invalidate();
607
}
608

609
void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow how,
610
                                    double mx, double my)
611
{
612
    double width, height;
613
    window->GetContentSize(&width, &height);
614

615
    int x = 20, y = 33 + LINE_HEIGHT;
616
    y -= scrollPos*(LINE_HEIGHT/2);
617

618
    if(how == PAINT) {
619
        int top = y - 28, bot = y + 4;
620
        uiCanvas->DrawRect(0, (int)width, top, bot,
621
                           /*fillColor=*/{ 30, 30, 30, 255 }, /*outlineColor=*/{});
622
    }
623

624
    Button *oldHovered = hoveredButton;
625
    if(how != PAINT) {
626
        hoveredButton = NULL;
627
    }
628

629
    double hoveredX = 0, hoveredY = 0;
630
    for(Button *button : buttons) {
631
        if(how == PAINT) {
632
            button->Draw(uiCanvas, x, y, (button == hoveredButton));
633
        } else if(mx > x - 2 && mx < x + 26 &&
634
                  my < y + 2 && my > y - 26) {
635
            hoveredButton = button;
636
            hoveredX = x - 2;
637
            hoveredY = y - 26;
638
            if(how == CLICK) {
639
                button->Click();
640
            }
641
        }
642

643
        x += button->AdvanceWidth();
644
    }
645

646
    if(how != PAINT && hoveredButton != oldHovered) {
647
        if(hoveredButton == NULL) {
648
            window->SetTooltip("", 0, 0, 0, 0);
649
        } else {
650
            window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28);
651
        }
652
        window->Invalidate();
653
    }
654
}
655

656
//----------------------------------------------------------------------------
657
// Given (x, y, z) = (h, s, v) in [0,6), [0,1], [0,1], return (x, y, z) =
658
// (r, g, b) all in [0, 1].
659
//----------------------------------------------------------------------------
660
Vector TextWindow::HsvToRgb(Vector hsv) {
661
    if(hsv.x >= 6) hsv.x -= 6;
662

663
    Vector rgb;
664
    double hmod2 = hsv.x;
665
    while(hmod2 >= 2) hmod2 -= 2;
666
    double x = (1 - fabs(hmod2 - 1));
667
    if(hsv.x < 1) {
668
        rgb = Vector::From(1, x, 0);
669
    } else if(hsv.x < 2) {
670
        rgb = Vector::From(x, 1, 0);
671
    } else if(hsv.x < 3) {
672
        rgb = Vector::From(0, 1, x);
673
    } else if(hsv.x < 4) {
674
        rgb = Vector::From(0, x, 1);
675
    } else if(hsv.x < 5) {
676
        rgb = Vector::From(x, 0, 1);
677
    } else {
678
        rgb = Vector::From(1, 0, x);
679
    }
680
    double c = hsv.y*hsv.z;
681
    double m = 1 - hsv.z;
682
    rgb = rgb.ScaledBy(c);
683
    rgb = rgb.Plus(Vector::From(m, m, m));
684

685
    return rgb;
686
}
687

688
std::shared_ptr<Pixmap> TextWindow::HsvPattern2d(int w, int h) {
689
    std::shared_ptr<Pixmap> pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h);
690
    for(size_t j = 0; j < pixmap->height; j++) {
691
        size_t p = pixmap->stride * j;
692
        for(size_t i = 0; i < pixmap->width; i++) {
693
            Vector hsv = Vector::From(6.0*i/(pixmap->width-1), 1.0*j/(pixmap->height-1), 1);
694
            Vector rgb = HsvToRgb(hsv);
695
            rgb = rgb.ScaledBy(255);
696
            pixmap->data[p++] = (uint8_t)rgb.x;
697
            pixmap->data[p++] = (uint8_t)rgb.y;
698
            pixmap->data[p++] = (uint8_t)rgb.z;
699
        }
700
    }
701
    return pixmap;
702
}
703

704
std::shared_ptr<Pixmap> TextWindow::HsvPattern1d(double hue, double sat, int w, int h) {
705
    std::shared_ptr<Pixmap> pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h);
706
    for(size_t i = 0; i < pixmap->height; i++) {
707
        size_t p = i * pixmap->stride;
708
        for(size_t j = 0; j < pixmap->width; j++) {
709
            Vector hsv = Vector::From(6*hue, sat, 1.0*(pixmap->width - 1 - j)/pixmap->width);
710
            Vector rgb = HsvToRgb(hsv);
711
            rgb = rgb.ScaledBy(255);
712
            pixmap->data[p++] = (uint8_t)rgb.x;
713
            pixmap->data[p++] = (uint8_t)rgb.y;
714
            pixmap->data[p++] = (uint8_t)rgb.z;
715
        }
716
    }
717
    return pixmap;
718
}
719

720
void TextWindow::ColorPickerDone() {
721
    RgbaColor rgb = editControl.colorPicker.rgb;
722
    EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF()));
723
}
724

725
bool TextWindow::DrawOrHitTestColorPicker(UiCanvas *uiCanvas, DrawOrHitHow how, bool leftDown,
726
                                          double x, double y)
727
{
728
    using Platform::Window;
729

730
    bool mousePointerAsHand = false;
731

732
    if(how == HOVER && !leftDown) {
733
        editControl.colorPicker.picker1dActive = false;
734
        editControl.colorPicker.picker2dActive = false;
735
    }
736

737
    if(!editControl.colorPicker.show) return false;
738
    if(how == CLICK || (how == HOVER && leftDown)) window->Invalidate();
739

740
    static const RgbaColor BaseColor[12] = {
741
        RGBi(255,   0,   0),
742
        RGBi(  0, 255,   0),
743
        RGBi(  0,   0, 255),
744

745
        RGBi(  0, 255, 255),
746
        RGBi(255,   0, 255),
747
        RGBi(255, 255,   0),
748

749
        RGBi(255, 127,   0),
750
        RGBi(255,   0, 127),
751
        RGBi(  0, 255, 127),
752
        RGBi(127, 255,   0),
753
        RGBi(127,   0, 255),
754
        RGBi(  0, 127, 255),
755
    };
756

757
    double width, height;
758
    window->GetContentSize(&width, &height);
759

760
    int px = LEFT_MARGIN + CHAR_WIDTH_*editControl.col;
761
    int py = (editControl.halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);
762

763
    py += LINE_HEIGHT + 5;
764

765
    static const int WIDTH = 16, HEIGHT = 12;
766
    static const int PITCH = 18, SIZE = 15;
767

768
    px = min(px, (int)width - (WIDTH*PITCH + 40));
769

770
    int pxm = px + WIDTH*PITCH + 11,
771
        pym = py + HEIGHT*PITCH + 7;
772

773
    int bw = 6;
774
    if(how == PAINT) {
775
        uiCanvas->DrawRect(px, pxm+bw, py, pym+bw,
776
                           /*fillColor=*/{ 50, 50, 50, 255 },
777
                           /*outlineColor=*/{},
778
                           /*zIndex=*/1);
779
        uiCanvas->DrawRect(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2),
780
                           /*fillColor=*/{ 0, 0, 0, 255 },
781
                           /*outlineColor=*/{},
782
                           /*zIndex=*/1);
783
    } else {
784
        if(x < px || x > pxm+(bw/2) ||
785
           y < py || y > pym+(bw/2))
786
        {
787
            return false;
788
        }
789
    }
790
    px += (bw/2);
791
    py += (bw/2);
792

793
    int i, j;
794
    for(i = 0; i < WIDTH/2; i++) {
795
        for(j = 0; j < HEIGHT; j++) {
796
            Vector rgb;
797
            RgbaColor d;
798
            if(i == 0 && j < 8) {
799
                d = SS.modelColor[j];
800
                rgb = Vector::From(d.redF(), d.greenF(), d.blueF());
801
            } else if(i == 0) {
802
                double a = (j - 8.0)/3.0;
803
                rgb = Vector::From(a, a, a);
804
            } else {
805
                d = BaseColor[j];
806
                rgb = Vector::From(d.redF(), d.greenF(), d.blueF());
807
                if(i >= 2 && i <= 4) {
808
                    double a = (i == 2) ? 0.2 : (i == 3) ? 0.3 : 0.4;
809
                    rgb = rgb.Plus(Vector::From(a, a, a));
810
                }
811
                if(i >= 5 && i <= 7) {
812
                    double a = (i == 5) ? 0.7 : (i == 6) ? 0.4 : 0.18;
813
                    rgb = rgb.ScaledBy(a);
814
                }
815
            }
816

817
            rgb = rgb.ClampWithin(0, 1);
818
            int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j;
819

820
            if(how == PAINT) {
821
                uiCanvas->DrawRect(sx, sx+SIZE, sy, sy+SIZE,
822
                                   /*fillColor=*/RGBf(rgb.x, rgb.y, rgb.z),
823
                                   /*outlineColor=*/{},
824
                                   /*zIndex=*/2);
825
            } else if(how == CLICK) {
826
                if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {
827
                    editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
828
                    ColorPickerDone();
829
                }
830
            } else if(how == HOVER) {
831
                if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {
832
                    mousePointerAsHand = true;
833
                }
834
            }
835
        }
836
    }
837

838
    int hxm, hym;
839
    int hx = px + 5, hy = py + 5;
840
    hxm = hx + PITCH*7 + SIZE;
841
    hym = hy + PITCH*2 + SIZE;
842
    if(how == PAINT) {
843
        uiCanvas->DrawRect(hx, hxm, hy, hym,
844
                           /*fillColor=*/editControl.colorPicker.rgb,
845
                           /*outlineColor=*/{},
846
                           /*zIndex=*/2);
847
    } else if(how == CLICK) {
848
        if(x >= hx && x <= hxm && y >= hy && y <= hym) {
849
            ColorPickerDone();
850
        }
851
    } else if(how == HOVER) {
852
        if(x >= hx && x <= hxm && y >= hy && y <= hym) {
853
            mousePointerAsHand = true;
854
        }
855
    }
856

857
    hy += PITCH*3;
858

859
    hxm = hx + PITCH*7 + SIZE;
860
    hym = hy + PITCH*1 + SIZE;
861
    // The one-dimensional thing to pick the color's value
862
    if(how == PAINT) {
863
        uiCanvas->DrawPixmap(HsvPattern1d(editControl.colorPicker.h,
864
                                          editControl.colorPicker.s,
865
                                          hxm-hx, hym-hy),
866
                             hx, hy, /*zIndex=*/2);
867

868
        int cx = hx+(int)((hxm-hx)*(1.0 - editControl.colorPicker.v));
869
        uiCanvas->DrawLine(cx, hy, cx, hym,
870
                           /*fillColor=*/{ 0, 0, 0, 255 },
871
                           /*outlineColor=*/{},
872
                           /*zIndex=*/3);
873
    } else if(how == CLICK ||
874
          (how == HOVER && leftDown && editControl.colorPicker.picker1dActive))
875
    {
876
        if(x >= hx && x <= hxm && y >= hy && y <= hym) {
877
            editControl.colorPicker.v = 1 - (x - hx)/(hxm - hx);
878

879
            Vector rgb = HsvToRgb(Vector::From(
880
                            6*editControl.colorPicker.h,
881
                            editControl.colorPicker.s,
882
                            editControl.colorPicker.v));
883
            editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
884

885
            editControl.colorPicker.picker1dActive = true;
886
        }
887
    }
888
    // and advance our vertical position
889
    hy += PITCH*2;
890

891
    hxm = hx + PITCH*7 + SIZE;
892
    hym = hy + PITCH*6 + SIZE;
893
    // Two-dimensional thing to pick a color by hue and saturation
894
    if(how == PAINT) {
895
        uiCanvas->DrawPixmap(HsvPattern2d(hxm-hx, hym-hy), hx, hy,
896
                             /*zIndex=*/2);
897

898
        int cx = hx+(int)((hxm-hx)*editControl.colorPicker.h),
899
            cy = hy+(int)((hym-hy)*editControl.colorPicker.s);
900
        uiCanvas->DrawLine(cx - 5, cy, cx + 5, cy,
901
                           /*fillColor=*/{ 255, 255, 255, 255 },
902
                           /*outlineColor=*/{},
903
                           /*zIndex=*/3);
904
        uiCanvas->DrawLine(cx, cy - 5, cx, cy + 5,
905
                           /*fillColor=*/{ 255, 255, 255, 255 },
906
                           /*outlineColor=*/{},
907
                           /*zIndex=*/3);
908
    } else if(how == CLICK ||
909
          (how == HOVER && leftDown && editControl.colorPicker.picker2dActive))
910
    {
911
        if(x >= hx && x <= hxm && y >= hy && y <= hym) {
912
            double h = (x - hx)/(hxm - hx),
913
                   s = (y - hy)/(hym - hy);
914
            editControl.colorPicker.h = h;
915
            editControl.colorPicker.s = s;
916

917
            Vector rgb = HsvToRgb(Vector::From(
918
                            6*editControl.colorPicker.h,
919
                            editControl.colorPicker.s,
920
                            editControl.colorPicker.v));
921
            editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);
922

923
            editControl.colorPicker.picker2dActive = true;
924
        }
925
    }
926

927
    window->SetCursor(mousePointerAsHand ?
928
                      Window::Cursor::HAND :
929
                      Window::Cursor::POINTER);
930
    return true;
931
}
932

933
void TextWindow::Paint() {
934
    if (!canvas) return;
935

936
    double width, height;
937
    window->GetContentSize(&width, &height);
938
    if(halfRows != (int)height / (LINE_HEIGHT/2))
939
        Resize();
940

941
    Camera camera = {};
942
    camera.width      = width;
943
    camera.height     = height;
944
    camera.pixelRatio = window->GetDevicePixelRatio();
945
    camera.gridFit    = (window->GetDevicePixelRatio() == 1);
946
    camera.LoadIdentity();
947
    camera.offset.x   = -camera.width  / 2.0;
948
    camera.offset.y   = -camera.height / 2.0;
949

950
    Lighting lighting = {};
951
    lighting.backgroundColor = RGBi(0, 0, 0);
952

953
    canvas->SetLighting(lighting);
954
    canvas->SetCamera(camera);
955
    canvas->StartFrame();
956

957
    UiCanvas uiCanvas = {};
958
    uiCanvas.canvas = canvas;
959
    uiCanvas.flip = true;
960

961
    int r, c, a;
962
    for(a = 0; a < 2; a++) {
963
        for(r = 0; r < rows; r++) {
964
            int ltop = top[r];
965
            if(ltop < (scrollPos-1)) continue;
966
            if(ltop > scrollPos+halfRows) break;
967

968
            for(c = 0; c < min(((int)width/CHAR_WIDTH_)+1, (int) MAX_COLS); c++) {
969
                int x = LEFT_MARGIN + c*CHAR_WIDTH_;
970
                int y = (ltop-scrollPos)*(LINE_HEIGHT/2) + 4;
971

972
                int fg = meta[r][c].fg;
973
                int bg = meta[r][c].bg;
974

975
                // On the first pass, all the background quads; on the next
976
                // pass, all the foreground (i.e., font) quads.
977
                if(a == 0) {
978
                    RgbaColor bgRgb = meta[r][c].bgRgb;
979
                    int bh = LINE_HEIGHT, adj = 0;
980
                    if(bg == 'z') {
981
                        bh = CHAR_HEIGHT;
982
                        adj += 2;
983
                    } else {
984
                        bgRgb = RgbaColor::FromFloat(bgColorTable[bg*3+0],
985
                                                     bgColorTable[bg*3+1],
986
                                                     bgColorTable[bg*3+2]);
987
                    }
988

989
                    if(bg != 'd') {
990
                        // Move the quad down a bit, so that the descenders
991
                        // still have the correct background.
992
                        uiCanvas.DrawRect(x, x + CHAR_WIDTH_, y + adj, y + adj + bh,
993
                                          /*fillColor=*/bgRgb, /*outlineColor=*/{});
994
                    }
995
                } else if(a == 1) {
996
                    RgbaColor fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],
997
                                                           fgColorTable[fg*3+1],
998
                                                           fgColorTable[fg*3+2]);
999
                    if(text[r][c] != ' ') {
1000
                        uiCanvas.DrawBitmapChar(text[r][c], x, y + CHAR_HEIGHT, fgRgb);
1001
                    }
1002

1003
                    // If this is a link and it's hovered, then draw the
1004
                    // underline
1005
                    if(meta[r][c].link && meta[r][c].link != 'n' &&
1006
                        (r == hoveredRow && c == hoveredCol))
1007
                    {
1008
                        int cs = c, cf = c;
1009
                        while(cs >= 0 && meta[r][cs].link &&
1010
                                         meta[r][cs].f    == meta[r][c].f &&
1011
                                         meta[r][cs].data == meta[r][c].data)
1012
                        {
1013
                            cs--;
1014
                        }
1015
                        cs++;
1016

1017
                        while(          meta[r][cf].link &&
1018
                                        meta[r][cf].f    == meta[r][c].f &&
1019
                                        meta[r][cf].data == meta[r][c].data)
1020
                        {
1021
                            cf++;
1022
                        }
1023

1024
                        // But don't underline checkboxes or radio buttons
1025
                        while(((text[r][cs] >= 0xe000 && text[r][cs] <= 0xefff) ||
1026
                                text[r][cs] == ' ') &&
1027
                              cs < cf)
1028
                        {
1029
                            cs++;
1030
                        }
1031

1032
                        // Always use the color of the rightmost character
1033
                        // in the link, so that underline is consistent color
1034
                        fg = meta[r][cf-1].fg;
1035
                        fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],
1036
                                                     fgColorTable[fg*3+1],
1037
                                                     fgColorTable[fg*3+2]);
1038
                        int yp = y + CHAR_HEIGHT;
1039
                        uiCanvas.DrawLine(LEFT_MARGIN + cs*CHAR_WIDTH_, yp,
1040
                                          LEFT_MARGIN + cf*CHAR_WIDTH_, yp,
1041
                                          fgRgb);
1042
                    }
1043
                }
1044
            }
1045
        }
1046
    }
1047

1048
    // The line to indicate the column of radio buttons that indicates the
1049
    // active group.
1050
    SS.GW.GroupSelection();
1051
    auto const &gs = SS.GW.gs;
1052
    // Make sure this test agrees with test to determine which screen is drawn
1053
    if(!SS.GW.pending.description && gs.n == 0 && gs.constraints == 0 &&
1054
        shown.screen == Screen::LIST_OF_GROUPS)
1055
    {
1056
        int x = 29, y = 70 + LINE_HEIGHT;
1057
        y -= scrollPos*(LINE_HEIGHT/2);
1058

1059
        RgbaColor color = RgbaColor::FromFloat(fgColorTable['t'*3+0],
1060
                                               fgColorTable['t'*3+1],
1061
                                               fgColorTable['t'*3+2]);
1062
        uiCanvas.DrawLine(x, y, x, y+40, color);
1063
    }
1064

1065
    // The header has some icons that are drawn separately from the text
1066
    DrawOrHitTestIcons(&uiCanvas, PAINT, 0, 0);
1067

1068
    // And we may show a color picker for certain editable fields
1069
    DrawOrHitTestColorPicker(&uiCanvas, PAINT, false, 0, 0);
1070

1071
    canvas->FlushFrame();
1072
    canvas->FinishFrame();
1073
    canvas->Clear();
1074
}
1075

1076
void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) {
1077
    using Platform::Window;
1078

1079
    if(SS.TW.window->IsEditorVisible() || SS.GW.window->IsEditorVisible()) {
1080
        if(DrawOrHitTestColorPicker(NULL, leftClick ? CLICK : HOVER, leftDown, x, y)) {
1081
            return;
1082
        }
1083

1084
        if(leftClick) {
1085
            HideEditControl();
1086
            SS.GW.window->HideEditor();
1087
        } else {
1088
            window->SetCursor(Window::Cursor::POINTER);
1089
        }
1090
        return;
1091
    }
1092

1093
    DrawOrHitTestIcons(NULL, leftClick ? CLICK : HOVER, x, y);
1094

1095
    GraphicsWindow::Selection ps = SS.GW.hover;
1096
    SS.GW.hover.Clear();
1097

1098
    int prevHoveredRow = hoveredRow,
1099
        prevHoveredCol = hoveredCol;
1100
    hoveredRow = 0;
1101
    hoveredCol = 0;
1102

1103
    // Find the corresponding character in the text buffer
1104
    int c = (int)((x - LEFT_MARGIN) / CHAR_WIDTH_);
1105
    int hh = (LINE_HEIGHT)/2;
1106
    y += scrollPos*hh;
1107
    int r;
1108
    for(r = 0; r < rows; r++) {
1109
        if(y >= top[r]*hh && y <= (top[r]+2)*hh) {
1110
            break;
1111
        }
1112
    }
1113
    if(r >= 0 && c >= 0 && r < rows && c < MAX_COLS) {
1114
        window->SetCursor(Window::Cursor::POINTER);
1115

1116
        hoveredRow = r;
1117
        hoveredCol = c;
1118

1119
        const auto &item = meta[r][c];
1120
        if(leftClick) {
1121
            if(item.link && item.f) {
1122
                (item.f)(item.link, item.data);
1123
                Show();
1124
                SS.GW.Invalidate();
1125
            }
1126
        } else {
1127
            if(item.link) {
1128
                window->SetCursor(Window::Cursor::HAND);
1129
                if(item.h) {
1130
                    (item.h)(item.link, item.data);
1131
                }
1132
            } else {
1133
                window->SetCursor(Window::Cursor::POINTER);
1134
            }
1135
        }
1136
    }
1137

1138
    if((!ps.Equals(&(SS.GW.hover))) ||
1139
        prevHoveredRow != hoveredRow ||
1140
        prevHoveredCol != hoveredCol)
1141
    {
1142
        SS.GW.Invalidate();
1143
        window->Invalidate();
1144
    }
1145
}
1146

1147
void TextWindow::MouseLeave() {
1148
    hoveredButton = NULL;
1149
    hoveredRow = 0;
1150
    hoveredCol = 0;
1151
    window->Invalidate();
1152
}
1153

1154
void TextWindow::ScrollbarEvent(double newPos) {
1155
    if(window->IsEditorVisible()) {
1156
        // An edit field is active. Do not move the scrollbar.
1157
        return;
1158
    }
1159

1160
    int bottom = top[rows-1] + 2;
1161
    newPos = min((int)newPos, bottom - halfRows);
1162
    newPos = max((int)newPos, 0);
1163
    if(newPos != scrollPos) {
1164
        scrollPos = (int)newPos;
1165
        window->SetScrollbarPosition(scrollPos);
1166
        window->Invalidate();
1167
    }
1168
}
1169

1170
}
1171

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.