function Panel_Palette_Mixer() : PanelContent() constructor { title = __txt("Palettes Mixer"); padding = ui(8); auto_pin = true; w = ui(320); h = ui(400); connect_surf = surface_create(1, 1); connect_blend_surf = surface_create(1, 1); content_surf = surface_create(1, 1); var _def = load_palette_mixer(); palette_data = _def != noone? _def : { nodes: [ { color : cola(c_black), x : -64, y : 0 }, { color : cola(c_white), x : 64, y : 0 }, ], connections: [ [ 0, 1 ], ], blends: [], }; palette = []; static centerView = function() { var _mx = 0, _my = 0; for (var i = 0, n = array_length(palette_data.nodes); i < n; i++) { var _node = palette_data.nodes[i]; _mx += _node.x; _my += _node.y; } mixer_x = n? -_mx / n : 0; mixer_y = n? -_my / n : 0; } centerView(); mixer_s = 1; mixer_dragging = false; mixer_drag_mx = 0; mixer_drag_my = 0; mixer_drag_sx = 0; mixer_drag_sy = 0; node_size = ui(PREFERENCES.panel_menu_palette_node_size); node_size_to = node_size; node_hovering = noone; node_dragging = noone; node_drag_mx = 0; node_drag_my = 0; node_drag_sx = 0; node_drag_sy = 0; node_selecting = noone; blnd_hovering = noone; conn_hovering = noone; connection_drag = noone; conn_menu_ctx = noone; pal_draging = false; pal_drag_mx = 0; pal_drag_my = 0; shade_mode = 0; pr_palette = ds_priority_create(); function setColor(clr) { if(node_selecting == noone) return; node_selecting.color = clr; CURRENT_COLOR = clr; } function drawContent(panel) { draw_clear_alpha(COLORS.panel_bg_clear, 0); var _palettes = palette_data.nodes; var _connections = palette_data.connections; var _blends = palette_data.blends; if(key_mod_press(SHIFT)) shade_mode = lerp_float(shade_mode, 1, 20); else shade_mode = lerp_float(shade_mode, 0, 10); if(!in_dialog) draw_sprite_stretched(THEME.ui_panel_bg, 1, 0, 0, w, h); #region blend points for (var i = 0, n = array_length(_blends); i < n; i++) { var _b = _blends[i]; var _fr = _palettes[_b.from]; var _to = _palettes[_b.to]; var _rt = _b.amount; _b.x = lerp(_fr.x, _to.x, _rt); _b.y = lerp(_fr.y, _to.y, _rt); _b.color = merge_color(_fr.color, _to.color, _rt); } #endregion #region palette var pal_s = ui(16); var pal_w = in_dialog? w - padding * 2 : w - ui(16 * 2); var col = floor(pal_w / pal_s); var row = ceil(array_length(_palettes) / col); var pal_h = pal_s * max(1, row); var pal_x = in_dialog? padding : ui(16); var pal_y = in_dialog? h - pal_h - padding : h - pal_h - ui(16); var pbg_x = pal_x - ui(8); var pbg_y = pal_y - ui(8); var pbg_w = pal_w + ui(16); var pbg_h = pal_h + ui(16); draw_sprite_stretched(THEME.button_def, 0, pbg_x, pbg_y, pbg_w, pbg_h); if(pHOVER && point_in_rectangle(mx, my, pbg_x, pbg_y, pbg_x + pbg_w, pbg_y + pbg_h)) { draw_sprite_stretched_ext(THEME.button_def, 3, pbg_x, pbg_y, pbg_w, pbg_h, c_white, 0.5); } ds_priority_clear(pr_palette); for (var i = 0, n = array_length(_palettes); i < n; i++) ds_priority_add(pr_palette, _palettes[i].color, _palettes[i].y * 10000 + _palettes[i].x); for (var i = 0, n = array_length(_blends); i < n; i++) ds_priority_add(pr_palette, _blends[i].color, _blends[i].y * 10000 + _blends[i].x); var _ind = 0; palette = []; while(!ds_priority_empty(pr_palette)) palette[_ind++] = ds_priority_delete_min(pr_palette); var _ppw = pal_w - ui(24 + 8); var _ppx = pal_x + ui(24 + 8); var _pw = pal_s; var _ph = pal_s; var amo = array_length(palette); var col = floor(_ppw / _pw); var row = ceil(amo / col); var cx = -1, cy = -1; var _pd = ui(5); var _h = row * _ph; _pw = _ppw / col; for(var i = 0; i < array_length(palette); i++) { draw_set_color(palette[i]); var _x0 = _ppx + safe_mod(i, col) * _pw; var _y0 = pal_y + floor(i / col) * _ph; draw_rectangle(_x0, _y0 + 1, _x0 + _pw, _y0 + _ph, false); if(node_selecting) { if(color_diff(node_selecting.color, palette[i]) < 0.01) { cx = _x0; cy = _y0; } } else if(color_diff(CURRENT_COLOR, palette[i]) < 0.01) { cx = _x0; cy = _y0; } if(pHOVER && point_in_rectangle(mx, my, _x0, _y0 + 1, _x0 + _pw, _y0 + _ph)) { if(mouse_press(mb_left)) { node_selecting = noone; CURRENT_COLOR = palette[i]; DRAGGING = { type: "Color", data: palette[i] } MESSAGE = DRAGGING; } } } if(cx) draw_sprite_stretched_ext(THEME.palette_selecting, 0, cx - _pd, cy + 1 - _pd, _pw + _pd * 2, _ph + _pd * 2); var _bx = pal_x; var _by = pal_y; var _bs = ui(24); var _b = buttonInstant(THEME.button_hide, _bx, _by, _bs, pal_h, [ mx, my ], pHOVER, pFOCUS, "", THEME.hamburger_s); if(_b == 2) { menuCall("", [ menuItem("Save palette as...", function() { var _path = get_save_filename_pxc("Hex paleete|*.hex", "Palette"); if(_path != "") { var _str = palette_string_hex(palette, false); file_text_write_all(_path, _str); var noti = log_message("PALETTE", $"Export palette complete.", THEME.noti_icon_tick, COLORS._main_value_positive, false); noti.path = _path; noti.setOnClick(function() { shellOpenExplorer(self.path); }, "Open in explorer", THEME.explorer); } }), ]); pal_draging = true; pal_drag_mx = mx; pal_drag_my = my; } if(pal_draging) { if(point_distance(pal_drag_mx, pal_drag_my, mx, my) > 8) { DRAGGING = { type: "Palette", data: palette }; MESSAGE = DRAGGING; pal_draging = false; instance_destroy(o_dialog_menubox); } if(mouse_release(mb_left)) pal_draging = false; } #endregion var px = padding; var py = padding; var pw = w - padding - padding; var ph = h - padding - padding - pal_h - ui(16); if(in_dialog) draw_sprite_stretched(THEME.ui_panel_bg, 1, px - ui(8), py - ui(8), pw + ui(16), ph + ui(16 - 4)); else ph -= ui(8); connect_surf = surface_verify(connect_surf, pw, ph); content_surf = surface_verify(content_surf, pw, ph); var _mx_x = pw / 2 + mixer_x; var _mx_y = ph / 2 + mixer_y; var _msx = (mx - px); var _msy = (my - py); var _mmx = mx - px - _mx_x; var _mmy = my - py - _mx_y; #region draw mixer surface_set_target(connect_surf) DRAW_CLEAR var _gs = key_mod_press(SHIFT)? ui(12) : node_size; var _gs2 = _gs / 2; var _ind = noone; var _hov = node_hovering; var _con_hover = conn_hovering; var _bln_hover = blnd_hovering; var _con_rat = 0; var _pHover = pHOVER && point_in_rectangle(mx, my, px, py, px + pw, py + ph); node_hovering = noone; conn_hovering = noone; blnd_hovering = noone; for (var i = 0, n = array_length(_connections); i < n; i++) { var conn = _connections[i]; if(!is_array(conn)) continue; var _fr = _palettes[conn[0]]; var _to = _palettes[conn[1]]; var _frx = round(_mx_x + _fr.x); var _fry = round(_mx_y + _fr.y); var _tox = round(_mx_x + _to.x); var _toy = round(_mx_y + _to.y); var _hv = _hov == noone && _con_hover == i; if(shade_mode == 0) { draw_set_alpha(0.75); draw_line_width_color(_frx, _fry, _tox, _toy, (_hv? 8 : 4) + 2, c_white, c_white); draw_set_alpha(1); } draw_line_width_color(_frx, _fry, _tox, _toy, _hv? 8 : 4, _fr.color, _to.color); if(_pHover && _bln_hover == noone && shade_mode == 0 && distance_to_line(_msx, _msy, _frx, _fry, _tox, _toy) < 6) { conn_hovering = i; var _d0 = point_distance(_frx, _fry, _msx, _msy); var _d1 = point_distance(_tox, _toy, _msx, _msy); _con_rat = _d0 / (_d0 + _d1); } } var _bs = key_mod_press(SHIFT)? ui(12) : node_size * 0.75; for (var i = 0, n = array_length(_blends); i < n; i++) { var _blend = _blends[i]; var _c = _blend.color; var _px = round(_mx_x + _blend.x); var _py = round(_mx_y + _blend.y); var _hv = _pHover && point_in_rectangle(_msx, _msy, _px - _bs / 2, _py - _bs / 2, _px + _bs / 2, _py + _bs / 2); draw_sprite_stretched_ext(THEME.s_box_r2, 0, _px - _bs / 2, _py - _bs / 2, _bs, _bs, _c, 1); if(shade_mode > 0) continue; BLEND_ADD draw_sprite_stretched_ext(THEME.s_box_r2, 1, _px - _bs / 2, _py - _bs / 2, _bs, _bs, c_white, 0.25 + 0.5 * (_bln_hover == i)); BLEND_NORMAL if(_hv) blnd_hovering = i; } if(connection_drag >= 0) { var _fr = _palettes[connection_drag]; var _frx = round(_mx_x + _fr.x); var _fry = round(_mx_y + _fr.y); if(_hov) { var _tox = round(_mx_x + _hov.x); var _toy = round(_mx_y + _hov.y); draw_line_width_color(_frx, _fry, _tox, _toy, 8, _fr.color, _hov.color); } else draw_line_width_color(_frx, _fry, _msx, _msy, 8, _fr.color, _fr.color); } surface_reset_target(); surface_set_target(content_surf) DRAW_CLEAR for (var i = 0, n = array_length(_palettes); i < n; i++) { var pal = _palettes[i]; var _c = pal.color; var _px = round(_mx_x + pal.x); var _py = round(_mx_y + pal.y); var _hv = _pHover && point_in_rectangle(_msx, _msy, _px - _gs2, _py - _gs2, _px + _gs2, _py + _gs2); if(shade_mode == 0) draw_sprite_stretched(THEME.button_def, _hov == pal, _px - _gs2, _py - _gs2, _gs, _gs); draw_sprite_stretched_ext(THEME.s_box_r2, 0, _px - _gs2 + 2, _py - _gs2 + 2, _gs - 4, _gs - 4, _c, 1); BLEND_ADD draw_sprite_stretched_ext(THEME.s_box_r2, 1, _px - _gs2 + 2, _py - _gs2 + 2, _gs - 4, _gs - 4, c_white, 0.25); BLEND_NORMAL if(shade_mode > 0) continue; if(pal == node_selecting) draw_sprite_stretched_ext(THEME.s_box_r2, 1, _px - _gs2, _py - _gs2, _gs, _gs, COLORS._main_accent, 1); if(_hv) { node_hovering = pal; _ind = i; } } if(node_hovering) { if(mouse_press(mb_left)) { node_selecting = node_hovering; node_dragging = node_hovering; node_drag_mx = mx; node_drag_my = my; node_drag_sx = node_hovering.x; node_drag_sy = node_hovering.y; CURRENT_COLOR = node_hovering.color; } if(DOUBLE_CLICK) { node_selecting = node_hovering; var dialog = dialogCall(o_dialog_color_selector, WIN_W / 2, WIN_H / 2); dialog.selector.onApply = setColor; dialog.onApply = setColor; dialog.setDefault(node_selecting.color); save_palette_mixer(palette_data); } if(mouse_press(mb_right)) connection_drag = _ind; } else if(blnd_hovering != noone) { if(mouse_click(mb_left)) { node_selecting = noone; CURRENT_COLOR = palette_data.blends[blnd_hovering].color; } if(mouse_press(mb_right)) { conn_menu_ctx = [ blnd_hovering ]; menuCall("", [ menuItem("Delete Blend point", function() { array_delete(palette_data.blends, conn_menu_ctx[0], 1); } ), ]); } } else if(conn_hovering != noone) { var conn = palette_data.connections[conn_hovering]; var _fr = _palettes[conn[0]]; var _to = _palettes[conn[1]]; var _cc = merge_color(_fr.color, _to.color, _con_rat); var _xx = lerp(_fr.x, _to.x, _con_rat); var _yy = lerp(_fr.y, _to.y, _con_rat); var _gs = ui(16); _xx = round(_mx_x + _xx); _yy = round(_mx_y + _yy); draw_sprite_stretched_ext(THEME.s_box_r2, 0, _xx - _gs / 2, _yy - _gs / 2, _gs, _gs, _cc, 1); BLEND_ADD draw_sprite_stretched_ext(THEME.button_def, 3, _xx - _gs / 2, _yy - _gs / 2, _gs, _gs, c_white, 0.25); BLEND_NORMAL if(DOUBLE_CLICK) { var _node = { color : _cc, x : _xx - _mx_x, y : _yy - _mx_y, }; var _idx = array_length(palette_data.nodes); array_push(palette_data.nodes, _node); array_delete(palette_data.connections, conn_hovering, 1); array_push( palette_data.connections, [ conn[0], _idx ]); array_push( palette_data.connections, [ _idx, conn[1] ]); node_selecting = _node; save_palette_mixer(palette_data); } if(mouse_click(mb_left)) { node_selecting = noone; CURRENT_COLOR = _cc; } if(mouse_press(mb_right)) { conn_menu_ctx = [ conn[0], conn[1], _con_rat ]; menuCall("", [ menuItem("New Blend point", function() { array_push(palette_data.blends, { from : conn_menu_ctx[0], to : conn_menu_ctx[1], amount : conn_menu_ctx[2] }) } ), ]); } } else if(_pHover) { if(mouse_press(mb_left)) node_selecting = noone; if(DOUBLE_CLICK) { var _node = { color : cola(c_black), x : value_snap(_mmx, 8), y : value_snap(_mmy, 8) }; array_push(palette_data.nodes, _node); node_selecting = _node; var dialog = dialogCall(o_dialog_color_selector, WIN_W / 2, WIN_H / 2); dialog.selector.onApply = setColor; dialog.onApply = setColor; dialog.setDefault(node_selecting.color); save_palette_mixer(palette_data); } if(mouse_press(mb_right)) { menuCall("", [ menuItem("Center view", function() { centerView(); } ), -1, menuItem("Save mixed...", function() { var _path = get_save_filename_pxc("JSON|*.json", "New mixed"); if(_path != "") save_palette_mixer(palette_data, _path); }, THEME.save), menuItem("Load mixed...", function() { var _path = get_open_filename_pxc("JSON|*.json", ""); if(_path != "") palette_data = load_palette_mixer(_path); }, THEME.noti_icon_file_load), -1, menuItem("Clear palette", function() { palette_data = { nodes: [], connections: [], blends: [], } }, THEME.cross), ]); } } if(connection_drag >= 0) { if(mouse_release(mb_right)) { if(_ind == noone) { var _pal = _palettes[connection_drag]; var _node = { color : _pal.color, x : value_snap(_mmx, 8), y : value_snap(_mmy, 8) }; array_push(palette_data.nodes, _node); var _conn = [ connection_drag, array_length(palette_data.nodes) - 1 ]; array_push(palette_data.connections, _conn); } else { var _exist = noone; var _conn = [ connection_drag, _ind ]; for (var i = 0, n = array_length(_connections); i < n; i++) { var conn = _connections[i]; if(array_equals(_conn, conn) || array_equals(_conn, [ conn[1], conn[0] ])) _exist = i; } if(_exist == noone) array_push(palette_data.connections, _conn); else array_delete(palette_data.connections, _exist, 1); save_palette_mixer(palette_data); } connection_drag = noone; } } if(node_dragging) { node_dragging.x = value_snap(node_drag_sx + (mx - node_drag_mx), 8); node_dragging.y = value_snap(node_drag_sy + (my - node_drag_my), 8); if(mouse_release(mb_left)) { node_dragging = false; save_palette_mixer(palette_data); } } if(node_selecting) { node_selecting.color = CURRENT_COLOR; if(keyboard_check_pressed(vk_delete)) { /////////////////// Node Delete var _delId = array_find(_palettes, node_selecting); array_delete(_palettes, _delId, 1); for (var i = array_length(_connections) - 1; i >= 0; i--) { var conn = _connections[i]; if(conn[0] == _delId || conn[1] == _delId) array_delete(palette_data.connections, i, 1); else { if(conn[0] > _delId) palette_data.connections[i][0]--; if(conn[1] > _delId) palette_data.connections[i][1]--; } } for (var i = array_length(_blends) - 1; i >= 0; i--) { var _blend = _blends[i]; if(_blend.from == _delId || _blend.to == _delId) array_delete(palette_data.connections, i, 1); else { if(_blend.from > _delId) _blend.from--; if(_blend.to > _delId) _blend.to--; } } save_palette_mixer(palette_data); } } if(_pHover && DRAGGING) { var _rx = value_snap(_mmx, 8); var _ry = value_snap(_mmy, 8); _xx = _rx; _yy = _ry; if(DRAGGING.type == "Color") { draw_sprite_stretched_ext(THEME.s_box_r2, 0, _mx_x + _xx - _gs / 2, _mx_y + _yy - _gs / 2, _gs, _gs, DRAGGING.data, 0.75); if(mouse_release(mb_left)) { var _node = { color : DRAGGING.data, x : _rx, y : _ry }; array_push(palette_data.nodes, _node); DRAGGING = noone; save_palette_mixer(palette_data); } } else if(DRAGGING.type == "Palette") { var _pal = DRAGGING.data; var _amo = array_length(_pal); var _pxs = array_create(_amo); var _pys = array_create(_amo); var _px = _xx; var _py = _yy; var _colRow, _colPrev, _colCurr; var _ligInc = 0; for (var i = 0; i < _amo; i++) { _colCurr = _pal[i]; if(i == 0) { _colRow = _colCurr; } else { var _lPrev = _color_get_light(_colPrev); var _lCurr = _color_get_light(_colCurr); var _sg = sign(_lCurr - _lPrev); if(_ligInc == 0) _ligInc = _sg; else if(_ligInc != _sg) { _ligInc = 0; _px = _xx; _py += _gs * 2; } } _pxs[i] = _px; _pys[i] = _py; draw_sprite_stretched_ext(THEME.s_box_r2, 0, _mx_x + _px - _gs / 2, _mx_y + _py - _gs / 2, _gs, _gs, _colCurr, 0.75); _px += _gs * 2; _colPrev = _colCurr; } if(mouse_release(mb_left)) { for (var i = 0; i < _amo; i++) { var _node = { color : _pal[i], x : _pxs[i], y : _pys[i] }; array_push(palette_data.nodes, _node); } DRAGGING = noone; save_palette_mixer(palette_data); } } } surface_reset_target(); #endregion #region draw surfaces if(shade_mode > 0) { var _shade_pal = array_create(array_length(palette) * 4); var _shade_pos = array_create(array_length(palette) * 2); var _ind = 0; for (var i = 0, n = array_length(_palettes); i < n; i++) { var _x = _palettes[i].x; var _y = _palettes[i].y; var _c = _palettes[i].color; _shade_pal[_ind * 4 + 0] = _color_get_red(_c); _shade_pal[_ind * 4 + 1] = _color_get_green(_c); _shade_pal[_ind * 4 + 2] = _color_get_blue(_c); _shade_pal[_ind * 4 + 3] = _color_get_alpha(_c); _shade_pos[_ind * 2 + 0] = _mx_x + _x; _shade_pos[_ind * 2 + 1] = _mx_y + _y; _ind++; } for (var i = 0, n = array_length(_blends); i < n; i++) { var _x = _blends[i].x; var _y = _blends[i].y; var _c = _blends[i].color; _shade_pal[_ind * 4 + 0] = _color_get_red(_c); _shade_pal[_ind * 4 + 1] = _color_get_green(_c); _shade_pal[_ind * 4 + 2] = _color_get_blue(_c); _shade_pal[_ind * 4 + 3] = _color_get_alpha(_c); _shade_pos[_ind * 2 + 0] = _mx_x + _x; _shade_pos[_ind * 2 + 1] = _mx_y + _y; _ind++; } connect_blend_surf = surface_verify(connect_blend_surf, pw, ph); surface_set_shader(connect_blend_surf, sh_palette_mixer_atlas_expand_palette); shader_set_f("dimension", pw, ph); shader_set_i("paletteSize", array_length(palette)); shader_set_f("palette", _shade_pal); shader_set_f("positions", _shade_pos); shader_set_f("influence", node_size / ui(4)); shader_set_f("progress", shade_mode); draw_surface_safe(connect_surf); surface_reset_shader(); draw_surface(connect_blend_surf, px, py); } else { shader_set(sh_FXAA); gpu_set_tex_filter(true); shader_set_f("dimension", pw, ph); shader_set_f("cornerDis", 0.5); shader_set_f("mixAmo", 1); draw_surface(connect_surf, px, py); gpu_set_tex_filter(false); shader_reset(); } draw_surface(content_surf, px, py); #endregion if(_pHover && mouse_press(mb_middle)) { mixer_dragging = true; mixer_drag_mx = mx; mixer_drag_my = my; mixer_drag_sx = mixer_x; mixer_drag_sy = mixer_y; } if(mixer_dragging) { mixer_x = round(mixer_drag_sx + (mx - mixer_drag_mx)); mixer_y = round(mixer_drag_sy + (my - mixer_drag_my)); if(mouse_release(mb_middle)) mixer_dragging = false; } draw_set_text(f_p2, fa_right, fa_bottom, COLORS._main_text_sub); var _gs = ui(16); var _nhx = px + pw - _gs; var _nhy = py + ph + ui(4); var _cc = noone; var _txt = ""; if(shade_mode > 0) { _cc = surface_getpixel(connect_blend_surf, mx - px, my - py); _txt = $"Sampled #{color_get_hex(_cc)}"; } else if(node_hovering) { _cc = node_hovering.color; _txt = $"Node #{color_get_hex(_cc)}"; } else if(blnd_hovering >= 0) { blnd_hovering = min(blnd_hovering, array_length(palette_data.blends) - 1); _cc = palette_data.blends[blnd_hovering].color; _txt = $"Blend point #{color_get_hex(_cc)}"; } else if(conn_hovering >= 0) { conn_hovering = min(conn_hovering, array_length(palette_data.connections) - 1); var conn = palette_data.connections[conn_hovering]; var _fr = _palettes[conn[0]]; var _to = _palettes[conn[1]]; _cc = merge_color(_fr.color, _to.color, _con_rat); _txt = $"Connection #{color_get_hex(_fr.color)} - #{color_get_hex(_to.color)} [{round(_con_rat * 100)}%]"; } if(_cc != noone) { draw_sprite_stretched_ext(THEME.s_box_r2, 0, _nhx, _nhy - _gs, _gs, _gs, _cc, 1); BLEND_ADD draw_sprite_stretched_ext(THEME.button_def, 3, _nhx, _nhy - _gs, _gs, _gs, c_white, 0.25); BLEND_NORMAL draw_text(_nhx - ui(4), _nhy, _txt); } if(pHOVER && key_mod_press(CTRL)) { if(mouse_wheel_down()) node_size_to = clamp(node_size_to - ui(4), ui(12), ui(64)); if(mouse_wheel_up()) node_size_to = clamp(node_size_to + ui(4), ui(12), ui(64)); } node_size = lerp_float(node_size, node_size_to, 3); } } function save_palette_mixer(data, path = "") { var _dirr = $"{DIRECTORY}/Palettes/Mixer"; directory_verify(_dirr); var _path = path == ""? _dirr + "/current.json" : path; json_save_struct(_path, data); } function load_palette_mixer(path = "") { var _dirr = $"{DIRECTORY}/Palettes/Mixer"; directory_verify(_dirr); var _path = path == ""? _dirr + "/current.json" : path; if(!file_exists(_path)) return noone; var _str = json_load_struct(_path); if(!struct_has(_str, "blends")) _str.blends = []; return _str; }