Solvespace
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
8namespace SolveSpace {9
10class Button {11public:12virtual std::string Tooltip() = 0;13virtual void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) = 0;14virtual int AdvanceWidth() = 0;15virtual void Click() = 0;16};17
18class SpacerButton : public Button {19public:20std::string Tooltip() override { return ""; }21
22void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override {23// Draw a darker-grey spacer in between the groups of icons.24uiCanvas->DrawRect(x, x + 4, y, y - 24,25/*fillColor=*/{ 45, 45, 45, 255 },26/*outlineColor=*/{});27}28
29int AdvanceWidth() override { return 12; }30
31void Click() override {}32};33
34class ShowHideButton : public Button {35public:36bool *variable;37std::string tooltip;38std::string iconName;39std::shared_ptr<Pixmap> icon;40
41ShowHideButton(bool *variable, std::string iconName, std::string tooltip)42: variable(variable), tooltip(tooltip), iconName(iconName) {}43
44std::string Tooltip() override {45return ((*variable) ? "Hide " : "Show ") + tooltip;46}47
48void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override {49if(icon == NULL) {50icon = LoadPng("icons/text-window/" + iconName + ".png");51}52
53uiCanvas->DrawPixmap(icon, x, y - 24);54if(asHovered) {55uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26,56/*fillColor=*/{ 255, 255, 0, 75 },57/*outlineColor=*/{});58}59if(!*(variable)) {60int s = 0, f = 23;61RgbaColor color = { 255, 0, 0, 150 };62uiCanvas->DrawLine(x+s, y-s, x+f, y-f, color, 2);63uiCanvas->DrawLine(x+s, y-f, x+f, y-s, color, 2);64}65}66
67int AdvanceWidth() override { return 32; }68
69void Click() override { SS.GW.ToggleBool(variable); }70};71
72class FacesButton : public ShowHideButton {73public:74FacesButton()75: ShowHideButton(&(SS.GW.showFaces), "faces", "") {}76
77std::string Tooltip() override {78if(*variable) {79return "Don't make faces selectable with mouse";80} else {81return "Make faces selectable with mouse";82}83}84};85
86#include <array>87class TriStateButton : public Button {88public:89static const size_t tri = 3;90
91TriStateButton(unsigned *variable, const std::array<unsigned, tri> &states,92const std::array<std::string, tri> &tooltips,93const std::array<std::string, tri> &iconNames)94: variable(variable), states(states), tooltips(tooltips), iconNames(iconNames) {95}96
97unsigned *const variable;98const std::array<unsigned, tri> states;99const std::array<std::string, tri> tooltips;100const std::array<std::string, tri> iconNames;101std::shared_ptr<Pixmap> icons[tri];102
103std::string Tooltip() override {104for(size_t k = 0; k < tri; ++k)105if(*variable == states[k])106return tooltips[k];107ssassert(false, "Unexpected mode");108}109
110void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override {111for(size_t k = 0; k < tri; ++k)112if(icons[k] == nullptr)113icons[k] = LoadPng("icons/text-window/" + iconNames[k] + ".png");114
115std::shared_ptr<Pixmap> icon;116for(size_t k = 0; k < tri; ++k)117if(*variable == states[k]) {118icon = icons[k];119break;120}121
122uiCanvas->DrawPixmap(icon, x, y - 24);123if(asHovered) {124uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26,125/*fillColor=*/{255, 255, 0, 75},126/*outlineColor=*/{});127}128}129
130
131int AdvanceWidth() override { return 32; }132
133void Click() override {134for(size_t k = 0; k < tri; ++k)135if(*variable == states[k]) {136*variable = states[(k + 1) % tri];137break;138}139
140SS.GenerateAll();141SS.GW.Invalidate();142SS.ScheduleShowTW();143}144};145
146static SpacerButton spacerButton;147
148static ShowHideButton workplanesButton =149{ &(SS.GW.showWorkplanes), "workplane", "workplanes from inactive groups" };150static ShowHideButton normalsButton =151{ &(SS.GW.showNormals), "normal", "normals" };152static ShowHideButton pointsButton =153{ &(SS.GW.showPoints), "point", "points" };154static ShowHideButton constructionButton =155{ &(SS.GW.showConstruction), "construction", "construction entities" };156static 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"}};163static FacesButton facesButton;164static ShowHideButton shadedButton =165{ &(SS.GW.showShaded), "shaded", "shaded view of solid model" };166static ShowHideButton edgesButton =167{ &(SS.GW.showEdges), "edges", "edges of solid model" };168static ShowHideButton outlinesButton =169{ &(SS.GW.showOutlines), "outlines", "outline of solid model" };170static ShowHideButton meshButton =171{ &(SS.GW.showMesh), "mesh", "triangle mesh of solid model" };172static 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
180static 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. */
197const TextWindow::Color TextWindow::fgColors[] = {198{ 'd', RGBi(255, 255, 255) }, // Default : white199{ 'l', RGBi(100, 200, 255) }, // links : blue200{ 't', RGBi(255, 200, 100) }, // tree/text : yellow201{ 'h', RGBi( 90, 90, 90) },202{ 's', RGBi( 40, 255, 40) }, // Ok : green203{ 'm', RGBi(200, 200, 0) },204{ 'r', RGBi( 0, 0, 0) }, // Reverse : black205{ 'x', RGBi(255, 20, 20) }, // Error : red206{ 'i', RGBi( 0, 255, 255) }, // Info : cyan207{ 'g', RGBi(128, 128, 128) }, // Disabled : gray208{ 'b', RGBi(200, 200, 200) },209{ 0, RGBi( 0, 0, 0) }210};211/** Background color codes. */
212const TextWindow::Color TextWindow::bgColors[] = {213{ 'd', RGBi( 0, 0, 0) }, // Default : black214{ 't', RGBi( 34, 15, 15) },215{ 'a', RGBi( 25, 25, 25) }, // Alternate : dark gray216{ 'r', RGBi(255, 255, 255) }, // Reverse : white217{ 0, RGBi( 0, 0, 0) }218};219
220void TextWindow::MakeColorTable(const Color *in, float *out) {221int i;222for(i = 0; in[i].c != 0; i++) {223int c = in[i].c;224ssassert(c >= 0 && c <= 255, "Unexpected color index");225out[c*3 + 0] = in[i].color.redF();226out[c*3 + 1] = in[i].color.greenF();227out[c*3 + 2] = in[i].color.blueF();228}229}
230
231void TextWindow::Init() {232if(!window) {233window = Platform::CreateWindow(Platform::Window::Kind::TOOL, SS.GW.window);234if(window) {235canvas = CreateRenderer();236
237using namespace std::placeholders;238window->onClose = []() {239SS.TW.HideEditControl();240SS.GW.showTextWindow = false;241SS.GW.EnsureValidActives();242};243window->onMouseEvent = [this](Platform::MouseEvent event) {244using Platform::MouseEvent;245
246if(event.type == MouseEvent::Type::PRESS ||247event.type == MouseEvent::Type::DBL_PRESS ||248event.type == MouseEvent::Type::MOTION) {249bool isClick = (event.type != MouseEvent::Type::MOTION);250bool leftDown = (event.button == MouseEvent::Button::LEFT);251this->MouseEvent(isClick, leftDown, event.x, event.y);252return true;253} else if(event.type == MouseEvent::Type::LEAVE) {254MouseLeave();255return true;256} else if(event.type == MouseEvent::Type::SCROLL_VERT) {257if (event.scrollDelta == 0) {258return true;259}260if (abs(event.scrollDelta) < 0.2) {261if (event.scrollDelta > 0) {262event.scrollDelta = 0.2;263} else {264event.scrollDelta = -0.2;265}266}267double offset = LINE_HEIGHT / 2 * event.scrollDelta;268ScrollbarEvent(window->GetScrollbarPosition() - offset);269}270return false;271};272window->onKeyboardEvent = SS.GW.window->onKeyboardEvent;273window->onRender = std::bind(&TextWindow::Paint, this);274window->onEditingDone = std::bind(&TextWindow::EditControlDone, this, _1);275window->onScrollbarAdjusted = std::bind(&TextWindow::ScrollbarEvent, this, _1);276window->SetMinContentSize(370, 370);277}278}279
280ClearSuper();281}
282
283void TextWindow::ClearSuper() {284// Ugly hack, but not so ugly as the next line285Platform::WindowRef oldWindow = std::move(window);286std::shared_ptr<ViewportCanvas> oldCanvas = canvas;287
288// Cannot use *this = {} here because TextWindow instances289// are 2.4MB long; this causes stack overflows in prologue290// when built with MSVC, even with optimizations.291memset(this, 0, sizeof(*this));292
293// Return old canvas294window = std::move(oldWindow);295canvas = oldCanvas;296
297HideEditControl();298
299MakeColorTable(fgColors, fgColorTable);300MakeColorTable(bgColors, bgColorTable);301
302ClearScreen();303Show();304}
305
306void TextWindow::HideEditControl() {307editControl.colorPicker.show = false;308if(window) {309window->HideEditor();310window->Invalidate();311}312}
313
314void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) {315if(halfRow < 0) halfRow = top[hoveredRow];316editControl.halfRow = halfRow;317editControl.col = col;318
319int x = LEFT_MARGIN + CHAR_WIDTH_*col;320int y = (halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);321
322double width, height;323window->GetContentSize(&width, &height);324window->ShowEditor(x, y + LINE_HEIGHT - 2, LINE_HEIGHT - 4,325width - x, /*isMonospace=*/true, str);326}
327
328void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb) {329SS.ScheduleShowTW();330
331editControl.colorPicker.show = true;332editControl.colorPicker.rgb = rgb;333editControl.colorPicker.h = 0;334editControl.colorPicker.s = 0;335editControl.colorPicker.v = 1;336ShowEditControl(col, ssprintf("%.2f, %.2f, %.2f", rgb.redF(), rgb.greenF(), rgb.blueF()));337}
338
339void TextWindow::ClearScreen() {340int i, j;341for(i = 0; i < MAX_ROWS; i++) {342for(j = 0; j < MAX_COLS; j++) {343text[i][j] = ' ';344meta[i][j].fg = 'd';345meta[i][j].bg = 'd';346meta[i][j].link = NOT_A_LINK;347}348top[i] = i*2;349}350rows = 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.
355static const char* endString = " **** End of Text Screen ****";356
357void TextWindow::Printf(bool halfLine, const char *fmt, ...) {358if(!canvas) return;359
360if(rows >= MAX_ROWS) return;361
362if(rows >= MAX_ROWS-2 && (fmt != endString)) {363// twice due to some half-row issues on resizing364Printf(halfLine, endString);365Printf(halfLine, endString);366return;367}368
369va_list vl;370va_start(vl, fmt);371
372int r, c;373r = rows;374top[r] = (r == 0) ? 0 : (top[r-1] + (halfLine ? 3 : 2));375rows++;376
377for(c = 0; c < MAX_COLS; c++) {378text[r][c] = ' ';379meta[r][c].link = NOT_A_LINK;380}381
382char fg = 'd';383char bg = 'd';384RgbaColor bgRgb = RGBi(0, 0, 0);385int link = NOT_A_LINK;386uint32_t data = 0;387LinkFunction *f = NULL, *h = NULL;388
389c = 0;390while(*fmt) {391char buf[1024];392
393if(*fmt == '%') {394fmt++;395if(*fmt == '\0') goto done;396strcpy(buf, "");397switch(*fmt) {398case 'd': {399int v = va_arg(vl, int);400sprintf(buf, "%d", v);401break;402}403case 'x': {404unsigned int v = va_arg(vl, unsigned int);405sprintf(buf, "%08x", v);406break;407}408case '@': {409double v = va_arg(vl, double);410sprintf(buf, "%.2f", v);411break;412}413case '2': {414double v = va_arg(vl, double);415sprintf(buf, "%s%.2f", v < 0 ? "" : " ", v);416break;417}418case '3': {419double v = va_arg(vl, double);420sprintf(buf, "%s%.3f", v < 0 ? "" : " ", v);421break;422}423case '#': {424double v = va_arg(vl, double);425sprintf(buf, "%.3f", v);426break;427}428case 's': {429char *s = va_arg(vl, char *);430memcpy(buf, s, min(sizeof(buf), strlen(s)+1));431break;432}433case 'c': {434// 'char' is promoted to 'int' when passed through '...'435int v = va_arg(vl, int);436if(v == 0) {437strcpy(buf, "");438} else {439sprintf(buf, "%c", v);440}441break;442}443case 'E':444fg = 'd';445// leave the background, though446link = NOT_A_LINK;447data = 0;448f = NULL;449h = NULL;450break;451
452case 'F':453case 'B': {454char cc = fmt[1]; // color code455RgbaColor *rgbPtr = NULL;456switch(cc) {457case 0: goto done; // truncated directive458case 'p': cc = (char)va_arg(vl, int); break;459case 'z': rgbPtr = va_arg(vl, RgbaColor *); break;460}461if(*fmt == 'F') {462fg = cc;463} else {464bg = cc;465if(rgbPtr) bgRgb = *rgbPtr;466}467fmt++;468break;469}470case 'L':471if(fmt[1] == '\0') goto done;472fmt++;473if(*fmt == 'p') {474link = va_arg(vl, int);475} else {476link = *fmt;477}478break;479
480case 'f':481f = va_arg(vl, LinkFunction *);482break;483
484case 'h':485h = va_arg(vl, LinkFunction *);486break;487
488case 'D': {489unsigned int v = va_arg(vl, unsigned int);490data = (uint32_t)v;491break;492}493case '%':494strcpy(buf, "%");495break;496}497} else {498utf8_iterator it2(fmt), it1 = it2++;499strncpy(buf, fmt, it2 - it1);500buf[it2 - it1] = '\0';501}502
503for(utf8_iterator it(buf); *it; ++it) {504for(size_t i = 0; i < canvas->GetBitmapFont()->GetWidth(*it); i++) {505if(c >= MAX_COLS) goto done;506text[r][c] = (i == 0) ? *it : ' ';507meta[r][c].fg = fg;508meta[r][c].bg = bg;509meta[r][c].bgRgb = bgRgb;510meta[r][c].link = link;511meta[r][c].data = data;512meta[r][c].f = f;513meta[r][c].h = h;514c++;515}516}517
518utf8_iterator it(fmt);519it++;520fmt = it.ptr();521}522while(c < MAX_COLS) {523meta[r][c].fg = fg;524meta[r][c].bg = bg;525meta[r][c].bgRgb = bgRgb;526c++;527}528
529done:530va_end(vl);531}
532
533void TextWindow::Show() {534if(SS.GW.pending.operation == GraphicsWindow::Pending::NONE) {535SS.GW.ClearPending(/*scheduleShowTW=*/false);536}537
538SS.GW.GroupSelection();539auto const &gs = SS.GW.gs;540
541// Make sure these tests agree with test used to draw indicator line on542// main list of groups screen.543if(SS.GW.pending.description) {544// A pending operation (that must be completed with the mouse in545// the graphics window) will preempt our usual display.546HideEditControl();547ShowHeader(false);548Printf(false, "");549Printf(false, "%s", SS.GW.pending.description);550Printf(true, "%Fl%f%Ll(cancel operation)%E",551&TextWindow::ScreenUnselectAll);552} else if((gs.n > 0 || gs.constraints > 0) &&553shown.screen != Screen::PASTE_TRANSFORMED)554{555if(edit.meaning != Edit::TTF_TEXT) HideEditControl();556ShowHeader(false);557DescribeSelection();558} else {559if(edit.meaning == Edit::TTF_TEXT) HideEditControl();560ShowHeader(true);561switch(shown.screen) {562default:563shown.screen = Screen::LIST_OF_GROUPS;564// fall through565case Screen::LIST_OF_GROUPS: ShowListOfGroups(); break;566case Screen::GROUP_INFO: ShowGroupInfo(); break;567case Screen::GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break;568case Screen::CONFIGURATION: ShowConfiguration(); break;569case Screen::STEP_DIMENSION: ShowStepDimension(); break;570case Screen::LIST_OF_STYLES: ShowListOfStyles(); break;571case Screen::STYLE_INFO: ShowStyleInfo(); break;572case Screen::PASTE_TRANSFORMED: ShowPasteTransformed(); break;573case Screen::EDIT_VIEW: ShowEditView(); break;574case Screen::TANGENT_ARC: ShowTangentArc(); break;575}576}577Printf(false, "");578
579// Make sure there's room for the color picker580if(editControl.colorPicker.show) {581int pickerHeight = 25;582int halfRow = editControl.halfRow;583if(top[rows-1] - halfRow < pickerHeight && rows < MAX_ROWS) {584rows++;585top[rows-1] = halfRow + pickerHeight;586}587}588
589if(window) Resize();590}
591
592void TextWindow::Resize()593{
594double width, height;595window->GetContentSize(&width, &height);596
597halfRows = (int)height / (LINE_HEIGHT/2);598
599int bottom = top[rows-1] + 2;600scrollPos = min(scrollPos, bottom - halfRows);601scrollPos = max(scrollPos, 0);602
603window->ConfigureScrollbar(0, top[rows - 1] + 1, halfRows);604window->SetScrollbarPosition(scrollPos);605window->SetScrollbarVisible(top[rows - 1] + 1 > halfRows);606window->Invalidate();607}
608
609void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow how,610double mx, double my)611{
612double width, height;613window->GetContentSize(&width, &height);614
615int x = 20, y = 33 + LINE_HEIGHT;616y -= scrollPos*(LINE_HEIGHT/2);617
618if(how == PAINT) {619int top = y - 28, bot = y + 4;620uiCanvas->DrawRect(0, (int)width, top, bot,621/*fillColor=*/{ 30, 30, 30, 255 }, /*outlineColor=*/{});622}623
624Button *oldHovered = hoveredButton;625if(how != PAINT) {626hoveredButton = NULL;627}628
629double hoveredX = 0, hoveredY = 0;630for(Button *button : buttons) {631if(how == PAINT) {632button->Draw(uiCanvas, x, y, (button == hoveredButton));633} else if(mx > x - 2 && mx < x + 26 &&634my < y + 2 && my > y - 26) {635hoveredButton = button;636hoveredX = x - 2;637hoveredY = y - 26;638if(how == CLICK) {639button->Click();640}641}642
643x += button->AdvanceWidth();644}645
646if(how != PAINT && hoveredButton != oldHovered) {647if(hoveredButton == NULL) {648window->SetTooltip("", 0, 0, 0, 0);649} else {650window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28);651}652window->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//----------------------------------------------------------------------------
660Vector TextWindow::HsvToRgb(Vector hsv) {661if(hsv.x >= 6) hsv.x -= 6;662
663Vector rgb;664double hmod2 = hsv.x;665while(hmod2 >= 2) hmod2 -= 2;666double x = (1 - fabs(hmod2 - 1));667if(hsv.x < 1) {668rgb = Vector::From(1, x, 0);669} else if(hsv.x < 2) {670rgb = Vector::From(x, 1, 0);671} else if(hsv.x < 3) {672rgb = Vector::From(0, 1, x);673} else if(hsv.x < 4) {674rgb = Vector::From(0, x, 1);675} else if(hsv.x < 5) {676rgb = Vector::From(x, 0, 1);677} else {678rgb = Vector::From(1, 0, x);679}680double c = hsv.y*hsv.z;681double m = 1 - hsv.z;682rgb = rgb.ScaledBy(c);683rgb = rgb.Plus(Vector::From(m, m, m));684
685return rgb;686}
687
688std::shared_ptr<Pixmap> TextWindow::HsvPattern2d(int w, int h) {689std::shared_ptr<Pixmap> pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h);690for(size_t j = 0; j < pixmap->height; j++) {691size_t p = pixmap->stride * j;692for(size_t i = 0; i < pixmap->width; i++) {693Vector hsv = Vector::From(6.0*i/(pixmap->width-1), 1.0*j/(pixmap->height-1), 1);694Vector rgb = HsvToRgb(hsv);695rgb = rgb.ScaledBy(255);696pixmap->data[p++] = (uint8_t)rgb.x;697pixmap->data[p++] = (uint8_t)rgb.y;698pixmap->data[p++] = (uint8_t)rgb.z;699}700}701return pixmap;702}
703
704std::shared_ptr<Pixmap> TextWindow::HsvPattern1d(double hue, double sat, int w, int h) {705std::shared_ptr<Pixmap> pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h);706for(size_t i = 0; i < pixmap->height; i++) {707size_t p = i * pixmap->stride;708for(size_t j = 0; j < pixmap->width; j++) {709Vector hsv = Vector::From(6*hue, sat, 1.0*(pixmap->width - 1 - j)/pixmap->width);710Vector rgb = HsvToRgb(hsv);711rgb = rgb.ScaledBy(255);712pixmap->data[p++] = (uint8_t)rgb.x;713pixmap->data[p++] = (uint8_t)rgb.y;714pixmap->data[p++] = (uint8_t)rgb.z;715}716}717return pixmap;718}
719
720void TextWindow::ColorPickerDone() {721RgbaColor rgb = editControl.colorPicker.rgb;722EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF()));723}
724
725bool TextWindow::DrawOrHitTestColorPicker(UiCanvas *uiCanvas, DrawOrHitHow how, bool leftDown,726double x, double y)727{
728using Platform::Window;729
730bool mousePointerAsHand = false;731
732if(how == HOVER && !leftDown) {733editControl.colorPicker.picker1dActive = false;734editControl.colorPicker.picker2dActive = false;735}736
737if(!editControl.colorPicker.show) return false;738if(how == CLICK || (how == HOVER && leftDown)) window->Invalidate();739
740static const RgbaColor BaseColor[12] = {741RGBi(255, 0, 0),742RGBi( 0, 255, 0),743RGBi( 0, 0, 255),744
745RGBi( 0, 255, 255),746RGBi(255, 0, 255),747RGBi(255, 255, 0),748
749RGBi(255, 127, 0),750RGBi(255, 0, 127),751RGBi( 0, 255, 127),752RGBi(127, 255, 0),753RGBi(127, 0, 255),754RGBi( 0, 127, 255),755};756
757double width, height;758window->GetContentSize(&width, &height);759
760int px = LEFT_MARGIN + CHAR_WIDTH_*editControl.col;761int py = (editControl.halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2);762
763py += LINE_HEIGHT + 5;764
765static const int WIDTH = 16, HEIGHT = 12;766static const int PITCH = 18, SIZE = 15;767
768px = min(px, (int)width - (WIDTH*PITCH + 40));769
770int pxm = px + WIDTH*PITCH + 11,771pym = py + HEIGHT*PITCH + 7;772
773int bw = 6;774if(how == PAINT) {775uiCanvas->DrawRect(px, pxm+bw, py, pym+bw,776/*fillColor=*/{ 50, 50, 50, 255 },777/*outlineColor=*/{},778/*zIndex=*/1);779uiCanvas->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 {784if(x < px || x > pxm+(bw/2) ||785y < py || y > pym+(bw/2))786{787return false;788}789}790px += (bw/2);791py += (bw/2);792
793int i, j;794for(i = 0; i < WIDTH/2; i++) {795for(j = 0; j < HEIGHT; j++) {796Vector rgb;797RgbaColor d;798if(i == 0 && j < 8) {799d = SS.modelColor[j];800rgb = Vector::From(d.redF(), d.greenF(), d.blueF());801} else if(i == 0) {802double a = (j - 8.0)/3.0;803rgb = Vector::From(a, a, a);804} else {805d = BaseColor[j];806rgb = Vector::From(d.redF(), d.greenF(), d.blueF());807if(i >= 2 && i <= 4) {808double a = (i == 2) ? 0.2 : (i == 3) ? 0.3 : 0.4;809rgb = rgb.Plus(Vector::From(a, a, a));810}811if(i >= 5 && i <= 7) {812double a = (i == 5) ? 0.7 : (i == 6) ? 0.4 : 0.18;813rgb = rgb.ScaledBy(a);814}815}816
817rgb = rgb.ClampWithin(0, 1);818int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j;819
820if(how == PAINT) {821uiCanvas->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) {826if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {827editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);828ColorPickerDone();829}830} else if(how == HOVER) {831if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) {832mousePointerAsHand = true;833}834}835}836}837
838int hxm, hym;839int hx = px + 5, hy = py + 5;840hxm = hx + PITCH*7 + SIZE;841hym = hy + PITCH*2 + SIZE;842if(how == PAINT) {843uiCanvas->DrawRect(hx, hxm, hy, hym,844/*fillColor=*/editControl.colorPicker.rgb,845/*outlineColor=*/{},846/*zIndex=*/2);847} else if(how == CLICK) {848if(x >= hx && x <= hxm && y >= hy && y <= hym) {849ColorPickerDone();850}851} else if(how == HOVER) {852if(x >= hx && x <= hxm && y >= hy && y <= hym) {853mousePointerAsHand = true;854}855}856
857hy += PITCH*3;858
859hxm = hx + PITCH*7 + SIZE;860hym = hy + PITCH*1 + SIZE;861// The one-dimensional thing to pick the color's value862if(how == PAINT) {863uiCanvas->DrawPixmap(HsvPattern1d(editControl.colorPicker.h,864editControl.colorPicker.s,865hxm-hx, hym-hy),866hx, hy, /*zIndex=*/2);867
868int cx = hx+(int)((hxm-hx)*(1.0 - editControl.colorPicker.v));869uiCanvas->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{876if(x >= hx && x <= hxm && y >= hy && y <= hym) {877editControl.colorPicker.v = 1 - (x - hx)/(hxm - hx);878
879Vector rgb = HsvToRgb(Vector::From(8806*editControl.colorPicker.h,881editControl.colorPicker.s,882editControl.colorPicker.v));883editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);884
885editControl.colorPicker.picker1dActive = true;886}887}888// and advance our vertical position889hy += PITCH*2;890
891hxm = hx + PITCH*7 + SIZE;892hym = hy + PITCH*6 + SIZE;893// Two-dimensional thing to pick a color by hue and saturation894if(how == PAINT) {895uiCanvas->DrawPixmap(HsvPattern2d(hxm-hx, hym-hy), hx, hy,896/*zIndex=*/2);897
898int cx = hx+(int)((hxm-hx)*editControl.colorPicker.h),899cy = hy+(int)((hym-hy)*editControl.colorPicker.s);900uiCanvas->DrawLine(cx - 5, cy, cx + 5, cy,901/*fillColor=*/{ 255, 255, 255, 255 },902/*outlineColor=*/{},903/*zIndex=*/3);904uiCanvas->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{911if(x >= hx && x <= hxm && y >= hy && y <= hym) {912double h = (x - hx)/(hxm - hx),913s = (y - hy)/(hym - hy);914editControl.colorPicker.h = h;915editControl.colorPicker.s = s;916
917Vector rgb = HsvToRgb(Vector::From(9186*editControl.colorPicker.h,919editControl.colorPicker.s,920editControl.colorPicker.v));921editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z);922
923editControl.colorPicker.picker2dActive = true;924}925}926
927window->SetCursor(mousePointerAsHand ?928Window::Cursor::HAND :929Window::Cursor::POINTER);930return true;931}
932
933void TextWindow::Paint() {934if (!canvas) return;935
936double width, height;937window->GetContentSize(&width, &height);938if(halfRows != (int)height / (LINE_HEIGHT/2))939Resize();940
941Camera camera = {};942camera.width = width;943camera.height = height;944camera.pixelRatio = window->GetDevicePixelRatio();945camera.gridFit = (window->GetDevicePixelRatio() == 1);946camera.LoadIdentity();947camera.offset.x = -camera.width / 2.0;948camera.offset.y = -camera.height / 2.0;949
950Lighting lighting = {};951lighting.backgroundColor = RGBi(0, 0, 0);952
953canvas->SetLighting(lighting);954canvas->SetCamera(camera);955canvas->StartFrame();956
957UiCanvas uiCanvas = {};958uiCanvas.canvas = canvas;959uiCanvas.flip = true;960
961int r, c, a;962for(a = 0; a < 2; a++) {963for(r = 0; r < rows; r++) {964int ltop = top[r];965if(ltop < (scrollPos-1)) continue;966if(ltop > scrollPos+halfRows) break;967
968for(c = 0; c < min(((int)width/CHAR_WIDTH_)+1, (int) MAX_COLS); c++) {969int x = LEFT_MARGIN + c*CHAR_WIDTH_;970int y = (ltop-scrollPos)*(LINE_HEIGHT/2) + 4;971
972int fg = meta[r][c].fg;973int bg = meta[r][c].bg;974
975// On the first pass, all the background quads; on the next976// pass, all the foreground (i.e., font) quads.977if(a == 0) {978RgbaColor bgRgb = meta[r][c].bgRgb;979int bh = LINE_HEIGHT, adj = 0;980if(bg == 'z') {981bh = CHAR_HEIGHT;982adj += 2;983} else {984bgRgb = RgbaColor::FromFloat(bgColorTable[bg*3+0],985bgColorTable[bg*3+1],986bgColorTable[bg*3+2]);987}988
989if(bg != 'd') {990// Move the quad down a bit, so that the descenders991// still have the correct background.992uiCanvas.DrawRect(x, x + CHAR_WIDTH_, y + adj, y + adj + bh,993/*fillColor=*/bgRgb, /*outlineColor=*/{});994}995} else if(a == 1) {996RgbaColor fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],997fgColorTable[fg*3+1],998fgColorTable[fg*3+2]);999if(text[r][c] != ' ') {1000uiCanvas.DrawBitmapChar(text[r][c], x, y + CHAR_HEIGHT, fgRgb);1001}1002
1003// If this is a link and it's hovered, then draw the1004// underline1005if(meta[r][c].link && meta[r][c].link != 'n' &&1006(r == hoveredRow && c == hoveredCol))1007{1008int cs = c, cf = c;1009while(cs >= 0 && meta[r][cs].link &&1010meta[r][cs].f == meta[r][c].f &&1011meta[r][cs].data == meta[r][c].data)1012{1013cs--;1014}1015cs++;1016
1017while( meta[r][cf].link &&1018meta[r][cf].f == meta[r][c].f &&1019meta[r][cf].data == meta[r][c].data)1020{1021cf++;1022}1023
1024// But don't underline checkboxes or radio buttons1025while(((text[r][cs] >= 0xe000 && text[r][cs] <= 0xefff) ||1026text[r][cs] == ' ') &&1027cs < cf)1028{1029cs++;1030}1031
1032// Always use the color of the rightmost character1033// in the link, so that underline is consistent color1034fg = meta[r][cf-1].fg;1035fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0],1036fgColorTable[fg*3+1],1037fgColorTable[fg*3+2]);1038int yp = y + CHAR_HEIGHT;1039uiCanvas.DrawLine(LEFT_MARGIN + cs*CHAR_WIDTH_, yp,1040LEFT_MARGIN + cf*CHAR_WIDTH_, yp,1041fgRgb);1042}1043}1044}1045}1046}1047
1048// The line to indicate the column of radio buttons that indicates the1049// active group.1050SS.GW.GroupSelection();1051auto const &gs = SS.GW.gs;1052// Make sure this test agrees with test to determine which screen is drawn1053if(!SS.GW.pending.description && gs.n == 0 && gs.constraints == 0 &&1054shown.screen == Screen::LIST_OF_GROUPS)1055{1056int x = 29, y = 70 + LINE_HEIGHT;1057y -= scrollPos*(LINE_HEIGHT/2);1058
1059RgbaColor color = RgbaColor::FromFloat(fgColorTable['t'*3+0],1060fgColorTable['t'*3+1],1061fgColorTable['t'*3+2]);1062uiCanvas.DrawLine(x, y, x, y+40, color);1063}1064
1065// The header has some icons that are drawn separately from the text1066DrawOrHitTestIcons(&uiCanvas, PAINT, 0, 0);1067
1068// And we may show a color picker for certain editable fields1069DrawOrHitTestColorPicker(&uiCanvas, PAINT, false, 0, 0);1070
1071canvas->FlushFrame();1072canvas->FinishFrame();1073canvas->Clear();1074}
1075
1076void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) {1077using Platform::Window;1078
1079if(SS.TW.window->IsEditorVisible() || SS.GW.window->IsEditorVisible()) {1080if(DrawOrHitTestColorPicker(NULL, leftClick ? CLICK : HOVER, leftDown, x, y)) {1081return;1082}1083
1084if(leftClick) {1085HideEditControl();1086SS.GW.window->HideEditor();1087} else {1088window->SetCursor(Window::Cursor::POINTER);1089}1090return;1091}1092
1093DrawOrHitTestIcons(NULL, leftClick ? CLICK : HOVER, x, y);1094
1095GraphicsWindow::Selection ps = SS.GW.hover;1096SS.GW.hover.Clear();1097
1098int prevHoveredRow = hoveredRow,1099prevHoveredCol = hoveredCol;1100hoveredRow = 0;1101hoveredCol = 0;1102
1103// Find the corresponding character in the text buffer1104int c = (int)((x - LEFT_MARGIN) / CHAR_WIDTH_);1105int hh = (LINE_HEIGHT)/2;1106y += scrollPos*hh;1107int r;1108for(r = 0; r < rows; r++) {1109if(y >= top[r]*hh && y <= (top[r]+2)*hh) {1110break;1111}1112}1113if(r >= 0 && c >= 0 && r < rows && c < MAX_COLS) {1114window->SetCursor(Window::Cursor::POINTER);1115
1116hoveredRow = r;1117hoveredCol = c;1118
1119const auto &item = meta[r][c];1120if(leftClick) {1121if(item.link && item.f) {1122(item.f)(item.link, item.data);1123Show();1124SS.GW.Invalidate();1125}1126} else {1127if(item.link) {1128window->SetCursor(Window::Cursor::HAND);1129if(item.h) {1130(item.h)(item.link, item.data);1131}1132} else {1133window->SetCursor(Window::Cursor::POINTER);1134}1135}1136}1137
1138if((!ps.Equals(&(SS.GW.hover))) ||1139prevHoveredRow != hoveredRow ||1140prevHoveredCol != hoveredCol)1141{1142SS.GW.Invalidate();1143window->Invalidate();1144}1145}
1146
1147void TextWindow::MouseLeave() {1148hoveredButton = NULL;1149hoveredRow = 0;1150hoveredCol = 0;1151window->Invalidate();1152}
1153
1154void TextWindow::ScrollbarEvent(double newPos) {1155if(window->IsEditorVisible()) {1156// An edit field is active. Do not move the scrollbar.1157return;1158}1159
1160int bottom = top[rows-1] + 2;1161newPos = min((int)newPos, bottom - halfRows);1162newPos = max((int)newPos, 0);1163if(newPos != scrollPos) {1164scrollPos = (int)newPos;1165window->SetScrollbarPosition(scrollPos);1166window->Invalidate();1167}1168}
1169
1170}
1171