#include "pch.h" #include "log.h" #include "node_panel_stroke.h" #include "canvas.h" #include "node_button.h" #include "app.h" #include "abr.h" Node* NodePanelStroke::clone_instantiate() const { return new NodePanelStroke(); } void NodePanelStroke::clone_finalize(Node* dest) const { NodePanelStroke* n = static_cast(dest); n->init_controls(); } void NodePanelStroke::init() { init_template("tpl-panel-stroke"); init_controls(); } bool NodePanelStroke::import_abr(const std::string& path) { ABR abr; LOG("ABR detected"); std::string name, base, ext; std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return false; base = m[1].str(); name = m[2].str(); ext = m[3].str(); if (!str_iequals(ext, "abr")) return false; if (!abr.open(path)) { LOG("ABR read failed"); return false; } int tot = abr.m_samples.size() + abr.m_patterns.size() + abr.m_presets.size(); std::atomic_int count(0); async_start(); auto pb = App::I.show_progress("Importing ABR"); app_redraw(); async_update(); async_end(); parallel_for(abr.m_samples.size(), [&](size_t i) //for (const auto& samp : abr.m_samples) { auto ii = abr.m_samples.begin(); std::advance(ii, i); const auto& samp = *ii; std::string path_high = App::I.data_path + "/brushes/" + samp.first + ".png"; std::string path_thumb = App::I.data_path + "/brushes/thumbs/" + samp.first + ".png"; auto padded = samp.second->resize_squared(glm::u8vec4(255)); //auto high = padded.resize_power2(); //high.save(path_high); samp.second->save(path_high); auto thumb = padded.resize(64, 64); thumb.save(path_thumb); async_start(); NodeButtonBrush* brush = new NodeButtonBrush; m_brush_popup->m_container->add_child(brush); brush->init(); brush->create(); brush->loaded(); brush->set_icon(path_thumb.c_str()); brush->thumb_path = path_thumb; brush->high_path = path_high; brush->brush_name = name; brush->m_user_brush = true; brush->on_click = std::bind(&NodePanelBrush::handle_click, m_brush_popup, std::placeholders::_1); count++; float prog = (float)count / (float)tot; pb->m_progress->SetWidthP(prog * 100.f); app_redraw(); async_update(); async_end(); }); parallel_for(abr.m_patterns.size(), [&](size_t i) //for (const auto& patt : abr.m_patterns) { auto ii = abr.m_patterns.begin(); std::advance(ii, i); const auto& patt = *ii; std::string path_high = App::I.data_path + "/patterns/" + patt.first + ".png"; std::string path_thumb = App::I.data_path + "/patterns/thumbs/" + patt.first + ".png"; patt.second->save(path_high); auto thumb = patt.second->resize(64, 64); thumb.save(path_thumb); async_start(); NodeButtonBrush* brush = new NodeButtonBrush; m_pattern_popup->m_container->add_child(brush); brush->init(); brush->create(); brush->loaded(); brush->set_icon(path_thumb.c_str()); brush->thumb_path = path_thumb; brush->high_path = path_high; brush->brush_name = name; brush->m_user_brush = true; brush->on_click = std::bind(&NodePanelBrush::handle_click, m_pattern_popup, std::placeholders::_1); count++; float prog = (float)count / (float)tot; pb->m_progress->SetWidthP(prog * 100.f); app_redraw(); async_update(); async_end(); }); auto brushes = abr.compute_brushes(App::I.data_path); for (const auto& pr : brushes) { auto presets = App::I.stroke->m_presets_popup; async_start(); if (pr->load()) { LOG("add preset %s", pr->m_name.c_str()); presets->add_brush(pr); } count++; float prog = (float)count / (float)tot; pb->m_progress->SetWidthP(prog * 100.f); app_redraw(); async_update(); async_end(); } async_start(); pb->destroy(); app_redraw(); async_update(); async_end(); //save(); } void NodePanelStroke::update_controls() { const auto& b = Canvas::I->m_current_brush; m_tip_size->m_value.x = m_curves[m_tip_size].to_slider(b->m_tip_size); m_tip_spacing->m_value.x = m_curves[m_tip_spacing].to_slider(b->m_tip_spacing); m_tip_flow->m_value.x = m_curves[m_tip_flow].to_slider(b->m_tip_flow); m_tip_opacity->m_value.x = b->m_tip_opacity; m_tip_angle->m_value.x = b->m_tip_angle; m_tip_angle_delay->m_value.x = b->m_tip_angle_delay; m_tip_wet->m_value.x = b->m_tip_wet; m_tip_noise->m_value.x = b->m_tip_noise; m_jitter_scale->m_value.x = b->m_jitter_scale; m_jitter_angle->m_value.x = b->m_jitter_angle; m_jitter_spread->m_value.x = b->m_jitter_spread; m_jitter_flow->m_value.x = b->m_jitter_flow; m_jitter_hue->m_value.x = b->m_jitter_hue; m_jitter_sat->m_value.x = b->m_jitter_sat; m_jitter_val->m_value.x = b->m_jitter_val; m_tip_angle_follow->checked = b->m_tip_angle_follow; m_tip_flow_pressure->checked = b->m_tip_flow_pressure; m_tip_size_pressure->checked = b->m_tip_size_pressure; m_tip_invert->checked = b->m_tip_invert; m_tip_flipx->checked = b->m_tip_flipx; m_tip_flipy->checked = b->m_tip_flipy; m_pattern_enabled->checked = b->m_pattern_enabled; m_dual_enabled->checked = b->m_dual_enabled; m_dual_scatter_axis->checked = b->m_dual_scatter_axis; m_dual_invert->checked = b->m_dual_invert; m_dual_flipx->checked = b->m_dual_flipx; m_dual_flipy->checked = b->m_dual_flipy; m_dual_randflip->checked = b->m_dual_randflip; m_tip_randflipx->checked = b->m_tip_randflipx; m_tip_randflipy->checked = b->m_tip_randflipy; m_dual_size->m_value.x = m_curves[m_dual_size].to_slider(b->m_dual_size); m_dual_spacing->m_value.x = m_curves[m_dual_spacing].to_slider(b->m_dual_spacing); m_dual_flow->m_value.x = m_curves[m_dual_flow].to_slider(b->m_dual_flow); m_dual_scatter->m_value.x = b->m_dual_scatter; m_tip_aspect->m_value.x = b->m_tip_aspect; m_dual_opacity->m_value.x = b->m_dual_opacity; m_dual_rotate->m_value.x = b->m_dual_rotate; m_pattern_eachsample->checked = b->m_pattern_eachsample; m_pattern_invert->checked = b->m_pattern_invert; m_pattern_flipx->checked = b->m_pattern_flipx; m_pattern_flipy->checked = b->m_pattern_flipy; m_pattern_rand_offset->checked = b->m_pattern_rand_offset; m_pattern_scale->m_value.x = m_curves[m_pattern_scale].to_slider(b->m_pattern_scale); m_pattern_brightness->m_value.x = b->m_pattern_brightness; m_pattern_contrast->m_value.x = b->m_pattern_contrast; m_pattern_depth->m_value.x = b->m_pattern_depth; m_blend_mode->set_index(b->m_blend_mode); m_dual_blend_mode->set_index(b->m_dual_blend_mode); m_pattern_blend_mode->set_index(b->m_pattern_blend_mode); m_preview->m_brush = b; m_preview->draw_stroke(); } void NodePanelStroke::init_fold(const std::string& name) { if (auto b = find(("button-unfold-" + name).c_str())) { b->on_click = [this,name](Node*) { find(("fold-" + name).c_str())->ToggleVisibility(); }; } } void NodePanelStroke::init_controls() { m_brush_popup = std::make_shared(); m_brush_popup->m_manager = m_manager; m_brush_popup->m_dir_name = "brushes"; m_brush_popup->init(); m_brush_popup->create(); m_brush_popup->loaded(); m_brush_popup->SetPositioning(YGPositionTypeAbsolute); m_brush_popup->SetSize(300, 400); m_brush_popup->m_mouse_ignore = false; m_brush_popup->m_flood_events = true; m_brush_popup->m_capture_children = false; m_pattern_popup = std::make_shared(); m_pattern_popup->m_manager = m_manager; m_pattern_popup->m_dir_name = "textures"; m_pattern_popup->init(); m_pattern_popup->create(); m_pattern_popup->loaded(); m_pattern_popup->SetPositioning(YGPositionTypeAbsolute); m_pattern_popup->SetSize(300, 400); m_pattern_popup->m_mouse_ignore = false; m_pattern_popup->m_flood_events = true; m_pattern_popup->m_capture_children = false; m_presets_popup = std::make_shared(); m_presets_popup->m_manager = m_manager; m_presets_popup->init(); m_presets_popup->create(); m_presets_popup->loaded(); m_presets_popup->SetPositioning(YGPositionTypeAbsolute); m_presets_popup->SetSize(300, 400); m_presets_popup->m_mouse_ignore = false; m_presets_popup->m_flood_events = true; m_presets_popup->m_capture_children = false; int br_idx = std::max(m_brush_popup->find_brush("Round-Hard"), 0); // init main brush auto b = std::make_shared(); b->load_tip(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx)); b->load_dual(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx)); b->load_pattern(m_pattern_popup->get_texture_path(0), m_pattern_popup->get_thumb_path(0)); b->m_tip_size = 30; b->m_tip_flow = .9f; b->m_tip_spacing = .1f; b->m_tip_opacity = 1.f; Canvas::I->m_current_brush = b; // BRUSH PRESETS m_preset_thumb = find("preset-thumb"); m_preset_thumb->m_use_mipmaps = true; m_preset_preview = find("preset-preview"); m_preset_preview->m_brush = b; m_preset_preview->draw_stroke(); m_preset_button = find("preset-button"); m_preset_button->on_click = [this](Node*) { auto screen = root()->m_size; glm::vec2 pos = m_preset_button->m_pos + glm::vec2(m_preset_button->m_size.x, 0); root()->add_child(m_presets_popup); auto tick = root()->add_child(); tick->SetPositioning(YGPositionTypeAbsolute); tick->SetSize(16, 32); tick->SetPosition(pos.x, pos.y + (m_preset_button->m_size.y - 32) * 0.5f); tick->set_image("data/ui/popup-tick.png"); root()->update(); if ((pos.y + m_presets_popup->m_size.y) > screen.y) pos.y = screen.y - m_presets_popup->m_size.y; if (pos.y < 0) pos.y = 0; m_presets_popup->SetPosition(pos.x + 16, pos.y); m_presets_popup->mouse_capture(); root()->update(); m_presets_popup->on_popup_close = [this, tick](Node*) { tick->destroy(); }; m_presets_popup->on_brush_changed = [this](Node* target, std::shared_ptr& b) { // don't change some params b->m_tip_size = Canvas::I->m_current_brush->m_tip_size; b->m_tip_color = Canvas::I->m_current_brush->m_tip_color; *Canvas::I->m_current_brush = *b; m_preview->draw_stroke(); m_preset_preview->draw_stroke(); m_brush_thumb->set_image(b->m_brush_thumb_path); m_dual_brush_thumb->set_image(b->m_dual_thumb_path); m_preset_thumb->set_image(b->m_brush_thumb_path); m_pattern_thumb->set_image(b->m_pattern_thumb_path); update_controls(); }; }; // BRUSH TIP SHAPE m_brush_thumb = find("tip-change-thumb"); m_brush_thumb->set_image(m_brush_popup->get_thumb_path(br_idx)); m_brush_button = find("tip-change"); m_brush_button->on_click = [this](Node*) { auto screen = root()->m_size; glm::vec2 pos = m_brush_button->m_pos + glm::vec2(m_brush_button->m_size.x, 0); root()->add_child(m_brush_popup); auto tick = root()->add_child(); tick->SetPositioning(YGPositionTypeAbsolute); tick->SetSize(16, 32); tick->SetPosition(pos.x, pos.y + (m_brush_button->m_size.y - 32) * 0.5f); tick->set_image("data/ui/popup-tick.png"); root()->update(); if ((pos.y + m_brush_popup->m_size.y) > screen.y) pos.y = screen.y - m_brush_popup->m_size.y; if (pos.y < 0) pos.y = 0; m_brush_popup->SetPosition(pos.x + 16, pos.y); m_brush_popup->mouse_capture(); root()->update(); m_brush_popup->on_popup_close = [this, tick](Node*) { tick->destroy(); }; m_brush_popup->on_brush_changed = [this](Node*, int index) { if (on_brush_changed) on_brush_changed(this, m_brush_popup->get_texture_path(index), m_brush_popup->get_thumb_path(index)); m_brush_thumb->set_image(m_brush_popup->get_thumb_path(index)); //m_brush_popup->mouse_release(); //m_brush_popup->parent->remove_child(m_brush_popup.get()); }; }; // DUAL BRUSH TIP SHAPE m_dual_brush_thumb = find("dual-change-thumb"); m_dual_brush_thumb->set_image(m_brush_popup->get_thumb_path(br_idx)); m_dual_brush_button = find("dual-change"); m_dual_brush_button->on_click = [this](Node*) { auto screen = root()->m_size; glm::vec2 pos = m_dual_brush_button->m_pos + glm::vec2(m_dual_brush_button->m_size.x, 0); root()->add_child(m_brush_popup); auto tick = root()->add_child(); tick->SetPositioning(YGPositionTypeAbsolute); tick->SetSize(16, 32); tick->SetPosition(pos.x, pos.y + (m_dual_brush_button->m_size.y - 32) * 0.5f); tick->set_image("data/ui/popup-tick.png"); root()->update(); if ((pos.y + m_brush_popup->m_size.y) > screen.y) pos.y = screen.y - m_brush_popup->m_size.y; if (pos.y < 0) pos.y = 0; m_brush_popup->SetPosition(pos.x + 16, pos.y); m_brush_popup->mouse_capture(); root()->update(); m_brush_popup->on_popup_close = [this, tick](Node*) { tick->destroy(); }; m_brush_popup->on_brush_changed = [this](Node*, int index) { m_dual_enabled->set_value(true, true); if (on_dual_changed) on_dual_changed(this, m_brush_popup->get_texture_path(index), m_brush_popup->get_thumb_path(index)); m_dual_brush_thumb->set_image(m_brush_popup->get_thumb_path(index)); }; }; // PATTERN IMAGE m_pattern_thumb = find("pattern-change-thumb"); m_pattern_thumb->set_image(m_pattern_popup->get_thumb_path(0)); m_pattern_button = find("pattern-change"); m_pattern_button->on_click = [this](Node*) { auto screen = root()->m_size; glm::vec2 pos = m_pattern_button->m_pos + glm::vec2(m_pattern_button->m_size.x, 0); root()->add_child(m_pattern_popup); auto tick = root()->add_child(); tick->SetPositioning(YGPositionTypeAbsolute); tick->SetSize(16, 32); tick->SetPosition(pos.x, pos.y + (m_pattern_button->m_size.y - 32) * 0.5f); tick->set_image("data/ui/popup-tick.png"); root()->update(); if ((pos.y + m_pattern_popup->m_size.y) > screen.y) pos.y = screen.y - m_pattern_popup->m_size.y; if (pos.y < 0) pos.y = 0; m_pattern_popup->SetPosition(pos.x + 16, pos.y); m_pattern_popup->mouse_capture(); root()->update(); m_pattern_popup->on_popup_close = [this, tick](Node*) { tick->destroy(); }; m_pattern_popup->on_brush_changed = [this](Node*, int index) { m_pattern_enabled->set_value(true, true); if (on_pattern_changed) on_pattern_changed(this, m_pattern_popup->get_texture_path(index), m_pattern_popup->get_thumb_path(index)); m_pattern_thumb->set_image(m_pattern_popup->get_thumb_path(index)); }; }; m_preview = find("canvas"); m_blend_mode = find("blend-mode"); m_blend_mode->on_select = [this](Node*, int index) { Canvas::I->m_current_brush->m_blend_mode = index; m_preview->draw_stroke(); if (on_stroke_change) on_stroke_change(this); }; init_slider(m_tip_size, "tip-size", &Brush::m_tip_size); init_slider(m_tip_spacing, "tip-spacing", &Brush::m_tip_spacing); init_slider(m_tip_flow, "tip-flow", &Brush::m_tip_flow); init_slider(m_tip_opacity, "tip-opacity", &Brush::m_tip_opacity); init_slider(m_tip_angle, "tip-angle", &Brush::m_tip_angle); init_slider(m_tip_angle_delay, "tip-angle-delay", &Brush::m_tip_angle_delay); init_slider(m_tip_mix, "tip-mix", &Brush::m_tip_mix); init_slider(m_tip_wet, "tip-wet", &Brush::m_tip_wet); init_slider(m_tip_noise, "tip-noise", &Brush::m_tip_noise); init_slider(m_tip_hue, "tip-hue", &Brush::m_tip_hue); init_slider(m_tip_sat, "tip-sat", &Brush::m_tip_sat); init_slider(m_tip_val, "tip-val", &Brush::m_tip_val); init_slider(m_jitter_scale, "jitter-scale", &Brush::m_jitter_scale); init_slider(m_jitter_angle, "jitter-angle", &Brush::m_jitter_angle); init_slider(m_jitter_spread, "jitter-spread", &Brush::m_jitter_spread); init_slider(m_jitter_flow, "jitter-flow", &Brush::m_jitter_flow); init_slider(m_jitter_hue, "jitter-hue", &Brush::m_jitter_hue); init_slider(m_jitter_sat, "jitter-sat", &Brush::m_jitter_sat); init_slider(m_jitter_val, "jitter-val", &Brush::m_jitter_val); init_checkbox(m_tip_angle_follow, "tip-angle-follow", &Brush::m_tip_angle_follow); init_checkbox(m_tip_flow_pressure, "tip-flow-pressure", &Brush::m_tip_flow_pressure); init_checkbox(m_tip_size_pressure, "tip-size-pressure", &Brush::m_tip_size_pressure); init_checkbox(m_tip_invert, "tip-invert", &Brush::m_tip_invert); init_checkbox(m_tip_flipx, "tip-flipx", &Brush::m_tip_flipx); init_checkbox(m_tip_flipy, "tip-flipy", &Brush::m_tip_flipy); init_checkbox(m_pattern_enabled, "pattern-enabled", &Brush::m_pattern_enabled); init_checkbox(m_dual_enabled, "dual-enabled", &Brush::m_dual_enabled); init_checkbox(m_dual_scatter_axis, "dual-scatter-axis", &Brush::m_dual_scatter_axis); init_checkbox(m_dual_invert, "dual-invert", &Brush::m_dual_invert); init_checkbox(m_dual_flipx, "dual-flipx", &Brush::m_dual_flipx); init_checkbox(m_dual_flipy, "dual-flipy", &Brush::m_dual_flipy); init_checkbox(m_dual_randflip, "dual-randflip", &Brush::m_dual_randflip); init_checkbox(m_tip_randflipx, "tip-randflipx", &Brush::m_tip_randflipx); init_checkbox(m_tip_randflipy, "tip-randflipy", &Brush::m_tip_randflipy); init_checkbox(m_pattern_eachsample, "pattern-eachsample", &Brush::m_pattern_eachsample); init_checkbox(m_pattern_invert, "pattern-invert", &Brush::m_pattern_invert); init_checkbox(m_pattern_flipx, "pattern-flipx", &Brush::m_pattern_flipx); init_checkbox(m_pattern_flipy, "pattern-flipy", &Brush::m_pattern_flipy); init_checkbox(m_pattern_rand_offset, "pattern-rand-offset", &Brush::m_pattern_rand_offset); init_slider(m_dual_size, "dual-size", &Brush::m_dual_size); init_slider(m_dual_spacing, "dual-spacing", &Brush::m_dual_spacing); init_slider(m_dual_scatter, "dual-scatter", &Brush::m_dual_scatter); init_slider(m_tip_aspect, "tip-aspect", &Brush::m_tip_aspect); init_slider(m_dual_opacity, "dual-opacity", &Brush::m_dual_opacity); init_slider(m_dual_flow, "dual-flow", &Brush::m_dual_flow); init_slider(m_dual_rotate, "dual-rotate", &Brush::m_dual_rotate); init_slider(m_pattern_scale, "pattern-scale", &Brush::m_pattern_scale); init_slider(m_pattern_brightness, "pattern-brightness", &Brush::m_pattern_brightness); init_slider(m_pattern_contrast, "pattern-contrast", &Brush::m_pattern_contrast); init_slider(m_pattern_depth, "pattern-depth", &Brush::m_pattern_depth); SliderCurve curve_cubic { [](float v) { return glm::pow(v, 3.f); }, [](float v) { return glm::pow(v, 1.f / 3.f); }, }; SliderCurve curve_quad { [](float v) { return glm::pow(v, 2.f); }, [](float v) { return glm::pow(v, 1.f / 2.f); }, }; SliderCurve curve_size1k_perc { [](float v) { float ret = 0; if (v > 0.00f) ret += glm::pow(glm::min(1.f, (v - 0.00f) / 0.50f), 2.f) * 1.f; if (v > 0.50f) ret += glm::min(1.f, (v - 0.50f) / 0.25f) * 2.f; if (v > 0.75f) ret += glm::min(1.f, (v - 0.75f) / 0.10f) * 4.f; if (v > 0.85f) ret += glm::min(1.f, (v - 0.85f) / 0.05f) * 5.f; if (v > 0.90f) ret += glm::min(1.f, (v - 0.90f) / 0.10f) * 10.f; return glm::max(.01f, ret); }, [](float v) { float ret = 0; if (v > 0.f) ret += glm::pow(glm::min(1.f, (v - 0.f) / 1.f), 1.f / 2.f) * 0.50f; if (v > 1.f) ret += glm::min(1.f, (v - 1.f) / 2.f) * 0.25f; if (v > 2.f) ret += glm::min(1.f, (v - 2.f) / 4.f) * 0.10f; if (v > 4.f) ret += glm::min(1.f, (v - 4.f) / 5.f) * 0.05f; if (v > 5.f) ret += glm::min(1.f, (v - 5.f) / 10.f) * 0.10f; return ret; }, }; SliderCurve curve_size5k_pix { [](float v) { float ret = 0; if (v > 0.00f) ret += glm::pow(glm::min(1.f, (v - 0.00f) / 0.50f), 2.f) * 100.f; if (v > 0.50f) ret += glm::min(1.f, (v - 0.50f) / 0.25f) * 200.f; if (v > 0.75f) ret += glm::min(1.f, (v - 0.75f) / 0.10f) * 400.f; if (v > 0.85f) ret += glm::min(1.f, (v - 0.85f) / 0.05f) * 1000.f; if (v > 0.90f) ret += glm::min(1.f, (v - 0.90f) / 0.10f) * 5000.f; return glm::max(1.f, ret); }, [](float v) { float ret = 0; if (v > 0.f) ret += glm::pow(glm::min(1.f, (v - 0.f) / 100.f), 1.f / 2.f) * 0.50f; if (v > 100.f) ret += glm::min(1.f, (v - 100.f) / 200.f) * 0.25f; if (v > 200.f) ret += glm::min(1.f, (v - 200.f) / 400.f) * 0.10f; if (v > 400.f) ret += glm::min(1.f, (v - 400.f) / 1000.f) * 0.05f; if (v > 1000.f) ret += glm::min(1.f, (v - 1000.f) / 5000.f) * 0.10f; return ret; }, }; m_curves[m_tip_size] = curve_size5k_pix; m_curves[m_tip_spacing] = curve_size1k_perc; m_curves[m_tip_flow] = curve_quad; m_curves[m_dual_size] = curve_size5k_pix; m_curves[m_dual_spacing] = curve_size1k_perc; m_curves[m_dual_flow] = curve_quad; m_curves[m_pattern_scale] = curve_size1k_perc; m_tip_aspect_reset = find("tip-aspect-reset"); m_tip_aspect_reset->on_click = [this](Node*) { m_tip_aspect->set_value(0.5); Canvas::I->m_current_brush->m_tip_aspect = 0.5f; m_preview->draw_stroke(); if (on_stroke_change) on_stroke_change(this); }; m_dual_blend_mode = find("dual-blend-mode"); m_dual_blend_mode->on_select = [this](Node*, int index) { Canvas::I->m_current_brush->m_dual_blend_mode = index; m_preview->draw_stroke(); if (on_stroke_change) on_stroke_change(this); }; m_pattern_blend_mode = find("pattern-blend-mode"); m_pattern_blend_mode->on_select = [this](Node*, int index) { Canvas::I->m_current_brush->m_pattern_blend_mode = index; m_preview->draw_stroke(); if (on_stroke_change) on_stroke_change(this); }; m_preview->m_brush = Canvas::I->m_current_brush; m_preview->draw_stroke(); init_fold("color"); init_fold("metrics"); init_fold("pattern"); init_fold("dualbrush"); init_fold("medium"); init_fold("colorvar"); init_fold("jitter"); update_controls(); } void NodePanelStroke::init_slider(NodeSliderH*& target, const char* id, float Brush::* prop) { target = find(id); target->on_value_changed = std::bind(&NodePanelStroke::handle_slide, this, prop, std::placeholders::_1, std::placeholders::_2); //m_canvas->m_brush->*prop = target->m_value.x; } void NodePanelStroke::handle_slide(float Brush::* prop, Node* target, float value) { auto curve = m_curves.find((NodeSliderH*)target); Canvas::I->m_current_brush.get()->*prop = curve != m_curves.end() ? curve->second.to_value(value) : value; m_preview->draw_stroke(); if (on_stroke_change) on_stroke_change(this); } void NodePanelStroke::init_checkbox(NodeCheckBox*& target, const char* id, bool Brush::* prop) { target = find(id); target->on_value_changed = std::bind(&NodePanelStroke::handle_checkbox, this, prop, std::placeholders::_1, std::placeholders::_2); Canvas::I->m_current_brush.get()->*prop = target->checked; } void NodePanelStroke::handle_checkbox(bool Brush::* prop, Node *target, bool value) { Canvas::I->m_current_brush.get()->*prop = value; m_preview->draw_stroke(); if (on_stroke_change) on_stroke_change(this); }