summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/gui.cpp326
1 files changed, 312 insertions, 14 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 9d27a09..9981f25 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -182,7 +182,8 @@ enum CtrlKind {
182 CK_BIND, 182 CK_BIND,
183 CK_SECTION, 183 CK_SECTION,
184 CK_INFO, 184 CK_INFO,
185 CK_BUTTON 185 CK_BUTTON,
186 CK_COLOR
186}; 187};
187 188
188enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET }; 189enum CtrlAction { ACT_NONE = 0, ACT_RELOAD, ACT_RESET };
@@ -193,9 +194,13 @@ struct Ctrl {
193 const char *desc; 194 const char *desc;
194 bool *bval; // CK_TOGGLE 195 bool *bval; // CK_TOGGLE
195 int *ival; // CK_SLIDER 196 int *ival; // CK_SLIDER
197 int *rgb; // CK_COLOR: three ints, 0..255
196 int min, max; 198 int min, max;
197 int action; // CK_BUTTON 199 int action; // CK_BUTTON
198 bool zero_unlimited; // slider: value 0 displays as "All" 200 bool zero_unlimited; // slider: value 0 displays as "All"
201 // CK_COLOR picker state, kept while the picker is open so grey colours do
202 // not lose their hue when the user drags value/saturation.
203 float hue, sat, val;
199}; 204};
200 205
201struct Card { 206struct Card {
@@ -247,6 +252,10 @@ static void add_ctrl(int card, int kind, const char *label, const char *desc,
247 c.max = max; 252 c.max = max;
248 c.action = action; 253 c.action = action;
249 c.zero_unlimited = false; 254 c.zero_unlimited = false;
255 c.rgb = nullptr;
256 c.hue = 0.0f;
257 c.sat = 0.0f;
258 c.val = 1.0f;
250 g_cards[card].n++; 259 g_cards[card].n++;
251} 260}
252static void add_toggle(int card, const char *label, const char *desc, 261static void add_toggle(int card, const char *label, const char *desc,
@@ -267,6 +276,12 @@ static void add_button(int card, const char *label, const char *desc,
267 int action) { 276 int action) {
268 add_ctrl(card, CK_BUTTON, label, desc, 0, 0, nullptr, nullptr, action); 277 add_ctrl(card, CK_BUTTON, label, desc, 0, 0, nullptr, nullptr, action);
269} 278}
279static void add_color(int card, const char *label, const char *desc,
280 int *rgb) {
281 int i = g_nctrl;
282 add_ctrl(card, CK_COLOR, label, desc, 0, 255, nullptr, nullptr, ACT_NONE);
283 g_ctrls[i].rgb = rgb;
284}
270 285
271static int ctrl_val(const Ctrl &c) { 286static int ctrl_val(const Ctrl &c) {
272 if (c.kind == CK_TOGGLE && c.bval) 287 if (c.kind == CK_TOGGLE && c.bval)
@@ -381,19 +396,11 @@ static void build_cards() {
381 add_toggle(tint, "World brushes", 396 add_toggle(tint, "World brushes",
382 "Tint world textures (sky and water excluded from this part).", 397 "Tint world textures (sky and water excluded from this part).",
383 &g_tint_world_on); 398 &g_tint_world_on);
384 add_slider(tint, "World red", "Red channel of the world tint (0-255).", 0, 399 add_color(tint, "World colour", "Click the swatch to open the picker.",
385 255, &g_tint_world_rgb[0]); 400 g_tint_world_rgb);
386 add_slider(tint, "World green", "Green channel of the world tint (0-255).", 0,
387 255, &g_tint_world_rgb[1]);
388 add_slider(tint, "World blue", "Blue channel of the world tint (0-255).", 0,
389 255, &g_tint_world_rgb[2]);
390 add_toggle(tint, "Skybox", "Tint the skybox textures.", &g_tint_sky_on); 401 add_toggle(tint, "Skybox", "Tint the skybox textures.", &g_tint_sky_on);
391 add_slider(tint, "Sky red", "Red channel of the sky tint (0-255).", 0, 255, 402 add_color(tint, "Sky colour", "Click the swatch to open the picker.",
392 &g_tint_sky_rgb[0]); 403 g_tint_sky_rgb);
393 add_slider(tint, "Sky green", "Green channel of the sky tint (0-255).", 0,
394 255, &g_tint_sky_rgb[1]);
395 add_slider(tint, "Sky blue", "Blue channel of the sky tint (0-255).", 0, 255,
396 &g_tint_sky_rgb[2]);
397 404
398 int spread = add_card(3, &g_spread, "Spread control", 405 int spread = add_card(3, &g_spread, "Spread control",
399 "Choose a server shot seed that tightens the cone.", 406 "Choose a server shot seed that tightens the cone.",
@@ -436,6 +443,8 @@ static int g_scroll_drag_dy = 0;
436static float g_scroll_drag_start = 0.0f; 443static float g_scroll_drag_start = 0.0f;
437static int g_hover_slider = -1; 444static int g_hover_slider = -1;
438static int g_slider_drag = -1; 445static int g_slider_drag = -1;
446static int g_picker_ctrl = -1; // CK_COLOR control with its picker expanded
447static int g_picker_drag = 0; // 1 = SV square, 2 = hue bar
439static int g_capture_card = -1; 448static int g_capture_card = -1;
440static QAngle g_frozen{0, 0, 0}; 449static QAngle g_frozen{0, 0, 0};
441static int g_mouse_x = 0, g_mouse_y = 0; 450static int g_mouse_x = 0, g_mouse_y = 0;
@@ -821,7 +830,40 @@ static int button_w(const char *label) {
821 return bw; 830 return bw;
822} 831}
823 832
833static int label_row_h(const Ctrl &c);
834static bool picker_open_for(const Ctrl &c);
835static int picker_panel_h();
836static bool card_in_category(int i);
837static Rect card_rect(int index, int y);
838static int card_h(const Card &c);
839
824static int row_h(const Ctrl &c) { 840static int row_h(const Ctrl &c) {
841 int h = label_row_h(c);
842 if (picker_open_for(c))
843 h += picker_panel_h();
844 return h;
845}
846
847// ---------------------------------------------------------------------------
848// Colour picker (CK_COLOR)
849// ---------------------------------------------------------------------------
850
851static constexpr int PICKER_SV_W = 180;
852static constexpr int PICKER_SV_H = 110;
853static constexpr int PICKER_HUE_H = 12;
854static constexpr int PICKER_GAP = 8;
855
856static bool picker_open_for(const Ctrl &c) {
857 return c.kind == CK_COLOR && g_picker_ctrl >= 0 &&
858 &c == &g_ctrls[g_picker_ctrl];
859}
860
861static int picker_panel_h() {
862 return S((float)(PICKER_GAP + PICKER_SV_H + PICKER_GAP + PICKER_HUE_H +
863 PICKER_GAP));
864}
865
866static int label_row_h(const Ctrl &c) {
825 if (c.kind == CK_BUTTON) 867 if (c.kind == CK_BUTTON)
826 return S(32.0f); 868 return S(32.0f);
827 int maxw = (c.kind == CK_INFO || c.kind == CK_SECTION) 869 int maxw = (c.kind == CK_INFO || c.kind == CK_SECTION)
@@ -837,6 +879,207 @@ static int row_h(const Ctrl &c) {
837 return h < minh ? minh : h; 879 return h < minh ? minh : h;
838} 880}
839 881
882static void hsv_to_rgb(float h, float s, float v, int &r, int &g, int &b) {
883 if (h < 0.0f) h = 0.0f;
884 h = fmodf(h, 360.0f);
885 if (h < 0.0f) h += 360.0f;
886 if (s < 0.0f) s = 0.0f;
887 if (s > 1.0f) s = 1.0f;
888 if (v < 0.0f) v = 0.0f;
889 if (v > 1.0f) v = 1.0f;
890 float c = v * s;
891 float x = c * (1.0f - fabsf(fmodf(h / 60.0f, 2.0f) - 1.0f));
892 float m = v - c;
893 float rr = 0.0f, gg = 0.0f, bb = 0.0f;
894 if (h < 60.0f) {
895 rr = c; gg = x;
896 } else if (h < 120.0f) {
897 rr = x; gg = c;
898 } else if (h < 180.0f) {
899 gg = c; bb = x;
900 } else if (h < 240.0f) {
901 gg = x; bb = c;
902 } else if (h < 300.0f) {
903 rr = x; bb = c;
904 } else {
905 rr = c; bb = x;
906 }
907 r = (int)((rr + m) * 255.0f + 0.5f);
908 g = (int)((gg + m) * 255.0f + 0.5f);
909 b = (int)((bb + m) * 255.0f + 0.5f);
910}
911
912static void rgb_to_hsv(int r, int g, int b, float &h, float &s, float &v) {
913 float rf = r / 255.0f, gf = g / 255.0f, bf = b / 255.0f;
914 float mx = fmaxf(rf, fmaxf(gf, bf));
915 float mn = fminf(rf, fminf(gf, bf));
916 float d = mx - mn;
917 v = mx;
918 s = mx > 0.0f ? d / mx : 0.0f;
919 if (d <= 0.0f) {
920 h = 0.0f;
921 return;
922 }
923 if (mx == rf)
924 h = 60.0f * fmodf((gf - bf) / d, 6.0f);
925 else if (mx == gf)
926 h = 60.0f * ((bf - rf) / d + 2.0f);
927 else
928 h = 60.0f * ((rf - gf) / d + 4.0f);
929 if (h < 0.0f)
930 h += 360.0f;
931}
932
933struct PickerRects {
934 Rect panel, sv, hue, preview;
935};
936
937static PickerRects picker_rects(const Rect &cr, int row_y, int base_h) {
938 PickerRects p{};
939 int px = cr.x + S(16.0f);
940 int py = row_y + base_h;
941 int pw = card_content_w() - S(32.0f);
942 if (pw > S(340.0f)) pw = S(340.0f);
943 p.panel = Rect{px, py, pw, picker_panel_h()};
944 int preview_w = S(84.0f);
945 int svw = S((float)PICKER_SV_W);
946 if (svw + preview_w + S(16.0f) > pw)
947 svw = pw - preview_w - S(16.0f);
948 if (svw < S(60.0f))
949 svw = S(60.0f);
950 p.sv = Rect{px + S(PICKER_GAP), py + S(PICKER_GAP), svw,
951 S((float)PICKER_SV_H)};
952 p.hue = Rect{p.sv.x, p.sv.y + p.sv.h + S(PICKER_GAP), svw,
953 S((float)PICKER_HUE_H)};
954 p.preview = Rect{p.sv.x + p.sv.w + S(12.0f), p.sv.y, preview_w, S(52.0f)};
955 return p;
956}
957
958// Widget row/card used to map a control index back to screen space. Needed by
959// the drag handler, which runs outside click/layout context.
960static bool control_rect(int ctrl_index, Rect &out_card, Rect &out_row) {
961 Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f),
962 g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)};
963 int y = content.y - (int)g_scroll + S(8.0f);
964 for (int i = 0; i < g_ncards; i++) {
965 if (!card_in_category(i))
966 continue;
967 Card &c = g_cards[i];
968 Rect cr = card_rect(i, y);
969 if (c.expanded) {
970 int by = cr.y + header_h(c) + S(6.0f);
971 for (int j = 0; j < c.n; j++) {
972 Ctrl &ct = g_ctrls[c.c0 + j];
973 int rh = row_h(ct);
974 if (c.c0 + j == ctrl_index) {
975 out_card = cr;
976 out_row = Rect{cr.x, by, cr.w, rh};
977 return true;
978 }
979 by += rh;
980 }
981 }
982 y += card_h(c);
983 }
984 return false;
985}
986
987static bool picker_rects_for(int ctrl_index, PickerRects &out) {
988 if (ctrl_index < 0 || ctrl_index >= g_nctrl)
989 return false;
990 Rect card, row;
991 if (!control_rect(ctrl_index, card, row))
992 return false;
993 Ctrl &ct = g_ctrls[ctrl_index];
994 out = picker_rects(card, row.y, label_row_h(ct));
995 return true;
996}
997
998static void picker_apply(Ctrl &ct, const PickerRects &p) {
999 if (!ct.rgb || p.sv.w <= 0 || p.hue.w <= 0)
1000 return;
1001 if (g_picker_drag == 1) {
1002 float s = (float)(g_mouse_x - p.sv.x) / (float)p.sv.w;
1003 float v = 1.0f - (float)(g_mouse_y - p.sv.y) / (float)p.sv.h;
1004 if (s < 0.0f) s = 0.0f;
1005 if (s > 1.0f) s = 1.0f;
1006 if (v < 0.0f) v = 0.0f;
1007 if (v > 1.0f) v = 1.0f;
1008 ct.sat = s;
1009 ct.val = v;
1010 } else if (g_picker_drag == 2) {
1011 float h = 360.0f * (float)(g_mouse_x - p.hue.x) / (float)p.hue.w;
1012 if (h < 0.0f) h = 0.0f;
1013 if (h >= 360.0f) h = 359.9f;
1014 ct.hue = h;
1015 }
1016 hsv_to_rgb(ct.hue, ct.sat, ct.val, ct.rgb[0], ct.rgb[1], ct.rgb[2]);
1017}
1018
1019static void draw_picker(const Rect &clip, const Rect &cr, const Ctrl &ct,
1020 int row_y) {
1021 PickerRects p = picker_rects(cr, row_y, label_row_h(ct));
1022 fill(p.panel, Col{20, 23, 29, 250}, &clip);
1023 outline(p.panel, C_BORDER, &clip);
1024
1025 // Saturation/value field: solid rows of a white->hue gradient scaled by V.
1026 int sx = S(4.0f);
1027 int sy = S(4.0f);
1028 if (sx < 2) sx = 2;
1029 if (sy < 2) sy = 2;
1030 for (int yy = 0; yy < p.sv.h; yy += sy) {
1031 float v = 1.0f - ((float)yy + (float)sy * 0.5f) / (float)p.sv.h;
1032 int rh = sy < p.sv.h - yy ? sy : p.sv.h - yy;
1033 for (int xx = 0; xx < p.sv.w; xx += sx) {
1034 float s = ((float)xx + (float)sx * 0.5f) / (float)p.sv.w;
1035 int r, g, b;
1036 hsv_to_rgb(ct.hue, s, v, r, g, b);
1037 int rw = sx < p.sv.w - xx ? sx : p.sv.w - xx;
1038 fill(Rect{p.sv.x + xx, p.sv.y + yy, rw, rh}, Col{r, g, b, 255}, &clip);
1039 }
1040 }
1041 outline(p.sv, Col{0, 0, 0, 255}, &clip);
1042
1043 // Hue bar.
1044 int hx = S(4.0f);
1045 if (hx < 2) hx = 2;
1046 for (int xx = 0; xx < p.hue.w; xx += hx) {
1047 float h = 360.0f * ((float)xx + (float)hx * 0.5f) / (float)p.hue.w;
1048 int r, g, b;
1049 hsv_to_rgb(h, 1.0f, 1.0f, r, g, b);
1050 int rw = hx < p.hue.w - xx ? hx : p.hue.w - xx;
1051 fill(Rect{p.hue.x + xx, p.hue.y, rw, p.hue.h}, Col{r, g, b, 255}, &clip);
1052 }
1053 outline(p.hue, Col{0, 0, 0, 255}, &clip);
1054
1055 // Selection markers.
1056 int mx = p.sv.x + (int)(ct.sat * (float)p.sv.w);
1057 int my = p.sv.y + (int)((1.0f - ct.val) * (float)p.sv.h);
1058 Rect svm{mx - S(4.0f), my - S(4.0f), S(8.0f), S(8.0f)};
1059 outline(svm, Col{0, 0, 0, 255}, &clip);
1060 Rect svm2{mx - S(3.0f), my - S(3.0f), S(6.0f), S(6.0f)};
1061 outline(svm2, Col{255, 255, 255, 255}, &clip);
1062
1063 int hxm = p.hue.x + (int)((ct.hue / 360.0f) * (float)p.hue.w);
1064 line(hxm, p.hue.y - S(2.0f), hxm, p.hue.y + p.hue.h + S(2.0f),
1065 Col{255, 255, 255, 255});
1066
1067 // Preview swatch and hex value.
1068 int pr = ct.rgb ? ct.rgb[0] : 255;
1069 int pg = ct.rgb ? ct.rgb[1] : 255;
1070 int pb = ct.rgb ? ct.rgb[2] : 255;
1071 fill(p.preview, Col{pr, pg, pb, 255}, &clip);
1072 outline(p.preview, C_BORDER, &clip);
1073 char hex[16];
1074 snprintf(hex, sizeof(hex), "#%02X%02X%02X", pr, pg, pb);
1075 text_in(clip, p.preview.x, p.preview.y + p.preview.h + S(8.0f), hex,
1076 g_font_body, g_body_h, p.preview.w, C_TEXT);
1077 char rgb[48];
1078 snprintf(rgb, sizeof(rgb), "%d %d %d", pr, pg, pb);
1079 text_in(clip, p.preview.x, p.preview.y + p.preview.h + S(8.0f) + g_body_h,
1080 rgb, g_font_body, g_body_h, p.preview.w, C_DIM);
1081}
1082
840static int body_h(const Card &c) { 1083static int body_h(const Card &c) {
841 if (!c.expanded) 1084 if (!c.expanded)
842 return 0; 1085 return 0;
@@ -877,6 +1120,8 @@ static void close_gui() {
877 g_dragging = false; 1120 g_dragging = false;
878 g_scroll_drag = false; 1121 g_scroll_drag = false;
879 g_slider_drag = -1; 1122 g_slider_drag = -1;
1123 g_picker_ctrl = -1;
1124 g_picker_drag = 0;
880 g_reset_armed = false; 1125 g_reset_armed = false;
881 input_clear_mouse_states(); 1126 input_clear_mouse_states();
882 if (g_sdl_ready) { 1127 if (g_sdl_ready) {
@@ -1115,6 +1360,37 @@ static void handle_mouse_click() {
1115 } 1360 }
1116 return; 1361 return;
1117 } 1362 }
1363 if (ct.kind == CK_COLOR) {
1364 int idx = c.c0 + j;
1365 int base = label_row_h(ct);
1366 bool is_open = (g_picker_ctrl == idx);
1367 if (is_open) {
1368 PickerRects p = picker_rects(cr, by, base);
1369 if (p.sv.has(mx, my)) {
1370 g_picker_drag = 1;
1371 picker_apply(g_ctrls[idx], p);
1372 return;
1373 }
1374 if (p.hue.has(mx, my)) {
1375 g_picker_drag = 2;
1376 picker_apply(g_ctrls[idx], p);
1377 return;
1378 }
1379 }
1380 Rect sw{cr.x + cr.w - S(96.0f), by + (base - S(20.0f)) / 2,
1381 S(72.0f), S(20.0f)};
1382 if (my < by + base || sw.has(mx, my)) {
1383 if (is_open) {
1384 g_picker_ctrl = -1;
1385 } else {
1386 g_picker_ctrl = idx;
1387 if (ct.rgb)
1388 rgb_to_hsv(ct.rgb[0], ct.rgb[1], ct.rgb[2], ct.hue, ct.sat,
1389 ct.val);
1390 }
1391 }
1392 return;
1393 }
1118 if (ct.kind == CK_BUTTON) { 1394 if (ct.kind == CK_BUTTON) {
1119 int bw = button_w(ct.label); 1395 int bw = button_w(ct.label);
1120 Rect b{cr.x + cr.w - S(16.0f) - bw, 1396 Rect b{cr.x + cr.w - S(16.0f) - bw,
@@ -1291,6 +1567,17 @@ void gui_handle_input() {
1291 } 1567 }
1292 } 1568 }
1293 1569
1570 if (g_picker_drag) {
1571 if (!g_mouse_down) {
1572 g_picker_drag = 0;
1573 config_save();
1574 } else if (g_picker_ctrl >= 0) {
1575 PickerRects p;
1576 if (picker_rects_for(g_picker_ctrl, p))
1577 picker_apply(g_ctrls[g_picker_ctrl], p);
1578 }
1579 }
1580
1294 if (g_scroll_drag) { 1581 if (g_scroll_drag) {
1295 if (!g_mouse_down) { 1582 if (!g_mouse_down) {
1296 g_scroll_drag = false; 1583 g_scroll_drag = false;
@@ -1472,7 +1759,8 @@ static void draw_card(const Rect &clip, int index, int y) {
1472 wrap_text(ct.label, maxw, g_body_h, lines); 1759 wrap_text(ct.label, maxw, g_body_h, lines);
1473 int lh = g_body_h + S(2.0f); 1760 int lh = g_body_h + S(2.0f);
1474 int text_h = (int)lines.size() * lh - S(2.0f); 1761 int text_h = (int)lines.size() * lh - S(2.0f);
1475 int ly = by + (rh - text_h) / 2; 1762 int base_h = label_row_h(ct);
1763 int ly = by + (base_h - text_h) / 2;
1476 draw_wrapped(clip, cr.x + S(16.0f), ly, ct.label, g_font_body, g_body_h, 1764 draw_wrapped(clip, cr.x + S(16.0f), ly, ct.label, g_font_body, g_body_h,
1477 maxw, C_TEXT, S(2.0f)); 1765 maxw, C_TEXT, S(2.0f));
1478 if (ct.kind == CK_TOGGLE) { 1766 if (ct.kind == CK_TOGGLE) {
@@ -1483,6 +1771,16 @@ static void draw_card(const Rect &clip, int index, int y) {
1483 on ? C_GOOD : C_DIM); 1771 on ? C_GOOD : C_DIM);
1484 } else if (ct.kind == CK_SLIDER) { 1772 } else if (ct.kind == CK_SLIDER) {
1485 draw_slider(clip, cr, by, rh, ct); 1773 draw_slider(clip, cr, by, rh, ct);
1774 } else if (ct.kind == CK_COLOR) {
1775 int pr = ct.rgb ? ct.rgb[0] : 255;
1776 int pg = ct.rgb ? ct.rgb[1] : 255;
1777 int pb = ct.rgb ? ct.rgb[2] : 255;
1778 Rect sw{cr.x + cr.w - S(96.0f),
1779 by + (base_h - S(20.0f)) / 2, S(72.0f), S(20.0f)};
1780 fill(sw, Col{pr, pg, pb, 255}, &clip);
1781 outline(sw, picker_open_for(ct) ? C_ACCENT : C_BORDER, &clip);
1782 if (picker_open_for(ct))
1783 draw_picker(clip, cr, ct, by);
1486 } 1784 }
1487 } 1785 }
1488 by += rh; 1786 by += rh;