#include "pch.h" #include "app_core/brush_ui.h" #include "log.h" #include "node_panel_stroke.h" #include "canvas.h" #include "node_button.h" #include "app.h" #include "abr.h" namespace { class LegacyBrushStrokeControlServices final : public pp::app::BrushStrokeControlServices { public: explicit LegacyBrushStrokeControlServices(NodePanelStroke& panel) : panel_(panel) {} void set_float_setting(pp::app::BrushStrokeFloatSetting setting, float value) override { auto& brush = *Canvas::I->m_current_brush; switch (setting) { case pp::app::BrushStrokeFloatSetting::tip_size: brush.m_tip_size = value; break; case pp::app::BrushStrokeFloatSetting::tip_spacing: brush.m_tip_spacing = value; break; case pp::app::BrushStrokeFloatSetting::tip_flow: brush.m_tip_flow = value; break; case pp::app::BrushStrokeFloatSetting::tip_opacity: brush.m_tip_opacity = value; break; case pp::app::BrushStrokeFloatSetting::tip_angle: brush.m_tip_angle = value; break; case pp::app::BrushStrokeFloatSetting::tip_angle_smooth: brush.m_tip_angle_smooth = value; break; case pp::app::BrushStrokeFloatSetting::tip_mix: brush.m_tip_mix = value; break; case pp::app::BrushStrokeFloatSetting::tip_wet: brush.m_tip_wet = value; break; case pp::app::BrushStrokeFloatSetting::tip_noise: brush.m_tip_noise = value; break; case pp::app::BrushStrokeFloatSetting::tip_hue: brush.m_tip_hue = value; break; case pp::app::BrushStrokeFloatSetting::tip_saturation: brush.m_tip_sat = value; break; case pp::app::BrushStrokeFloatSetting::tip_value: brush.m_tip_val = value; break; case pp::app::BrushStrokeFloatSetting::jitter_scale: brush.m_jitter_scale = value; break; case pp::app::BrushStrokeFloatSetting::jitter_angle: brush.m_jitter_angle = value; break; case pp::app::BrushStrokeFloatSetting::jitter_scatter: brush.m_jitter_scatter = value; break; case pp::app::BrushStrokeFloatSetting::jitter_flow: brush.m_jitter_flow = value; break; case pp::app::BrushStrokeFloatSetting::jitter_opacity: brush.m_jitter_opacity = value; break; case pp::app::BrushStrokeFloatSetting::jitter_hue: brush.m_jitter_hue = value; break; case pp::app::BrushStrokeFloatSetting::jitter_saturation: brush.m_jitter_sat = value; break; case pp::app::BrushStrokeFloatSetting::jitter_value: brush.m_jitter_val = value; break; case pp::app::BrushStrokeFloatSetting::jitter_aspect: brush.m_jitter_aspect = value; break; case pp::app::BrushStrokeFloatSetting::dual_size: brush.m_dual_size = value; break; case pp::app::BrushStrokeFloatSetting::dual_spacing: brush.m_dual_spacing = value; break; case pp::app::BrushStrokeFloatSetting::dual_scatter: brush.m_dual_scatter = value; break; case pp::app::BrushStrokeFloatSetting::tip_aspect: brush.m_tip_aspect = value; break; case pp::app::BrushStrokeFloatSetting::dual_opacity: brush.m_dual_opacity = value; break; case pp::app::BrushStrokeFloatSetting::dual_flow: brush.m_dual_flow = value; break; case pp::app::BrushStrokeFloatSetting::dual_rotate: brush.m_dual_rotate = value; break; case pp::app::BrushStrokeFloatSetting::pattern_scale: brush.m_pattern_scale = value; break; case pp::app::BrushStrokeFloatSetting::pattern_brightness: brush.m_pattern_brightness = value; break; case pp::app::BrushStrokeFloatSetting::pattern_contrast: brush.m_pattern_contrast = value; break; case pp::app::BrushStrokeFloatSetting::pattern_depth: brush.m_pattern_depth = value; break; } } void set_bool_setting(pp::app::BrushStrokeBoolSetting setting, bool value) override { auto& brush = *Canvas::I->m_current_brush; switch (setting) { case pp::app::BrushStrokeBoolSetting::tip_angle_init: brush.m_tip_angle_init = value; break; case pp::app::BrushStrokeBoolSetting::tip_angle_follow: brush.m_tip_angle_follow = value; break; case pp::app::BrushStrokeBoolSetting::tip_flow_pressure: brush.m_tip_flow_pressure = value; break; case pp::app::BrushStrokeBoolSetting::tip_opacity_pressure: brush.m_tip_opacity_pressure = value; break; case pp::app::BrushStrokeBoolSetting::tip_size_pressure: brush.m_tip_size_pressure = value; break; case pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis: brush.m_jitter_scatter_bothaxis = value; break; case pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis: brush.m_jitter_aspect_bothaxis = value; break; case pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample: brush.m_jitter_hsv_eachsample = value; break; case pp::app::BrushStrokeBoolSetting::tip_invert: brush.m_tip_invert = value; break; case pp::app::BrushStrokeBoolSetting::tip_flip_x: brush.m_tip_flipx = value; break; case pp::app::BrushStrokeBoolSetting::tip_flip_y: brush.m_tip_flipy = value; break; case pp::app::BrushStrokeBoolSetting::pattern_enabled: brush.m_pattern_enabled = value; break; case pp::app::BrushStrokeBoolSetting::dual_enabled: brush.m_dual_enabled = value; break; case pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis: brush.m_dual_scatter_bothaxis = value; break; case pp::app::BrushStrokeBoolSetting::dual_invert: brush.m_dual_invert = value; break; case pp::app::BrushStrokeBoolSetting::dual_flip_x: brush.m_dual_flipx = value; break; case pp::app::BrushStrokeBoolSetting::dual_flip_y: brush.m_dual_flipy = value; break; case pp::app::BrushStrokeBoolSetting::dual_random_flip: brush.m_dual_randflip = value; break; case pp::app::BrushStrokeBoolSetting::tip_random_flip_x: brush.m_tip_randflipx = value; break; case pp::app::BrushStrokeBoolSetting::tip_random_flip_y: brush.m_tip_randflipy = value; break; case pp::app::BrushStrokeBoolSetting::pattern_each_sample: brush.m_pattern_eachsample = value; break; case pp::app::BrushStrokeBoolSetting::pattern_invert: brush.m_pattern_invert = value; break; case pp::app::BrushStrokeBoolSetting::pattern_flip_x: brush.m_pattern_flipx = value; break; case pp::app::BrushStrokeBoolSetting::pattern_flip_y: brush.m_pattern_flipy = value; break; case pp::app::BrushStrokeBoolSetting::pattern_random_offset: brush.m_pattern_rand_offset = value; break; } } void set_blend_mode(pp::app::BrushStrokeBlendSetting setting, int blend_mode) override { auto& brush = *Canvas::I->m_current_brush; switch (setting) { case pp::app::BrushStrokeBlendSetting::tip: brush.m_blend_mode = blend_mode; break; case pp::app::BrushStrokeBlendSetting::dual: brush.m_dual_blend_mode = blend_mode; break; case pp::app::BrushStrokeBlendSetting::pattern: brush.m_pattern_blend_mode = blend_mode; break; } } void reset_tip_aspect(float value) override { panel_.m_tip_aspect->set_value(value); Canvas::I->m_current_brush->m_tip_aspect = value; } void reset_default_brush() override { auto brush = std::make_shared(); brush->load_tip( panel_.m_brush_popup->get_texture_path(panel_.m_default_brush_index), panel_.m_brush_popup->get_thumb_path(panel_.m_default_brush_index)); brush->m_tip_size = 30; brush->m_tip_flow = .9f; brush->m_tip_spacing = .1f; brush->m_tip_opacity = 1.f; Canvas::I->m_current_brush = brush; } void update_stroke_controls() override { panel_.update_controls(); } void refresh_stroke_preview() override { if (panel_.m_preview) { panel_.m_preview->m_brush = Canvas::I->m_current_brush; } } void notify_stroke_changed() override { if (panel_.on_stroke_change) { panel_.on_stroke_change(&panel_); } } private: NodePanelStroke& panel_; }; } // namespace 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_file("data/dialogs/panel-stroke.xml", "tpl-panel-stroke"); init_controls(); } void NodePanelStroke::update_controls() { const auto& b = Canvas::I->m_current_brush; m_tip_size->m_value = m_curves[m_tip_size].to_slider(b->m_tip_size); m_tip_spacing->m_value = m_curves[m_tip_spacing].to_slider(b->m_tip_spacing); m_tip_flow->m_value = m_curves[m_tip_flow].to_slider(b->m_tip_flow); m_tip_opacity->m_value = b->m_tip_opacity; m_tip_angle->m_value = b->m_tip_angle; m_tip_angle_smooth->m_value = b->m_tip_angle_smooth; m_tip_mix->m_value = m_curves[m_tip_mix].to_slider(b->m_tip_mix); m_tip_wet->m_value = b->m_tip_wet; m_tip_noise->m_value = b->m_tip_noise; m_jitter_scale->m_value = b->m_jitter_scale; m_jitter_angle->m_value = b->m_jitter_angle; m_jitter_scatter->m_value = m_curves[m_jitter_scatter].to_slider(b->m_jitter_scatter);; m_jitter_flow->m_value = b->m_jitter_flow; m_jitter_opacity->m_value = b->m_jitter_opacity; m_jitter_hue->m_value = b->m_jitter_hue; m_jitter_sat->m_value = b->m_jitter_sat; m_jitter_val->m_value = b->m_jitter_val; m_jitter_hsv_eachsample->set_value(b->m_jitter_hsv_eachsample); m_jitter_aspect->m_value = b->m_jitter_aspect; m_tip_angle_follow->set_value(b->m_tip_angle_follow); m_tip_angle_init->set_value(b->m_tip_angle_init); m_tip_flow_pressure->set_value(b->m_tip_flow_pressure); m_tip_opacity_pressure->set_value(b->m_tip_opacity_pressure); m_tip_size_pressure->set_value(b->m_tip_size_pressure); m_jitter_aspect_bothaxis->set_value(b->m_jitter_aspect_bothaxis); m_tip_invert->set_value(b->m_tip_invert); m_tip_flipx->set_value(b->m_tip_flipx); m_tip_flipy->set_value(b->m_tip_flipy); m_pattern_enabled->set_value(b->m_pattern_enabled); m_dual_enabled->set_value(b->m_dual_enabled); m_dual_scatter_bothaxis->set_value(b->m_dual_scatter_bothaxis); m_dual_invert->set_value(b->m_dual_invert); m_dual_flipx->set_value(b->m_dual_flipx); m_dual_flipy->set_value(b->m_dual_flipy); m_dual_randflip->set_value(b->m_dual_randflip); m_tip_randflipx->set_value(b->m_tip_randflipx); m_tip_randflipy->set_value(b->m_tip_randflipy); m_dual_size->m_value = m_curves[m_dual_size].to_slider(b->m_dual_size); m_dual_spacing->m_value = m_curves[m_dual_spacing].to_slider(b->m_dual_spacing); m_dual_flow->m_value = m_curves[m_dual_flow].to_slider(b->m_dual_flow); m_dual_scatter->m_value = m_curves[m_dual_scatter].to_slider(b->m_dual_scatter); m_tip_aspect->m_value = b->m_tip_aspect; m_dual_opacity->m_value = b->m_dual_opacity; m_dual_rotate->m_value = b->m_dual_rotate; m_pattern_eachsample->set_value(b->m_pattern_eachsample); m_pattern_invert->set_value(b->m_pattern_invert); m_pattern_flipx->set_value(b->m_pattern_flipx); m_pattern_flipy->set_value(b->m_pattern_flipy); m_pattern_rand_offset->set_value(b->m_pattern_rand_offset); m_pattern_scale->m_value = m_curves[m_pattern_scale].to_slider(b->m_pattern_scale); m_pattern_brightness->m_value = b->m_pattern_brightness; m_pattern_contrast->m_value = b->m_pattern_contrast; m_pattern_depth->m_value = 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(); if (m_brush_thumb->m_path != b->m_brush_thumb_path) m_brush_thumb->set_image(b->m_brush_thumb_path); if (m_dual_brush_thumb->m_path != b->m_dual_thumb_path) m_dual_brush_thumb->set_image(b->m_dual_thumb_path); if (m_pattern_thumb->m_path != b->m_pattern_thumb_path) m_pattern_thumb->set_image(b->m_pattern_thumb_path); } void NodePanelStroke::set_flow(float value, bool normalized, bool propagate) { float v = normalized ? value : m_curves[m_tip_flow].to_slider(value); m_tip_flow->set_value(v, propagate); } void NodePanelStroke::set_size(float value, bool normalized, bool propagate) { float v = normalized ? value : m_curves[m_tip_size].to_value(value); m_tip_size->set_value(v, propagate); } 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->set_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->set_manager(m_manager); m_pattern_popup->m_dir_name = "patterns"; 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(YGUndefined, 400); //m_presets_popup->m_mouse_ignore = false; //m_presets_popup->m_flood_events = true; //m_presets_popup->m_capture_children = false; m_default_brush_index = std::max(m_brush_popup->find_brush("Round-Hard"), 0); const int br_idx = m_default_brush_index; // 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_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(App::I->presets); 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"); float hh = App::I->presets->m_container->m_children.size() > 10 ? App::I->height / App::I->zoom * .75f : 400.f; App::I->presets->SetHeight(glm::max(hh, 400.f)); App::I->presets->SetWidth(300); root()->update(); if ((pos.y + App::I->presets->m_size.y) > screen.y) pos.y = screen.y - App::I->presets->m_size.y; if (pos.y < 0) pos.y = 0; App::I->presets->SetPosition(pos.x + 16, pos.y); App::I->presets->SetPositioning(YGPositionTypeAbsolute); App::I->presets->m_mouse_ignore = false; App::I->presets->m_flood_events = true; App::I->presets->m_capture_children = false; App::I->presets->mouse_capture(); root()->update(); App::I->presets->on_popup_close = [this, tick](Node*) { tick->destroy(); }; App::I->presets->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; auto old_color = Canvas::I->m_current_brush->m_tip_color; *Canvas::I->m_current_brush = *b; Canvas::I->m_current_brush->m_tip_color = old_color; Canvas::I->m_current_brush->load(); //m_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_pattern_thumb->set_image(b->m_pattern_thumb_path); update_controls(); if (on_stroke_change) on_stroke_change(this); }; }; // 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->m_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_preview->m_draw_first = true; m_blend_mode = find("blend-mode"); m_blend_mode->on_select = [this](Node*, int index) { const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::tip, index); if (plan) { execute_stroke_control_plan(plan.value()); } }; init_slider(m_tip_size, "tip-size", pp::app::BrushStrokeFloatSetting::tip_size, &Brush::m_tip_size); init_slider(m_tip_spacing, "tip-spacing", pp::app::BrushStrokeFloatSetting::tip_spacing, &Brush::m_tip_spacing); init_slider(m_tip_flow, "tip-flow", pp::app::BrushStrokeFloatSetting::tip_flow, &Brush::m_tip_flow); init_slider(m_tip_opacity, "tip-opacity", pp::app::BrushStrokeFloatSetting::tip_opacity, &Brush::m_tip_opacity); init_slider(m_tip_angle, "tip-angle", pp::app::BrushStrokeFloatSetting::tip_angle, &Brush::m_tip_angle); init_slider(m_tip_angle_smooth, "tip-angle-smooth", pp::app::BrushStrokeFloatSetting::tip_angle_smooth, &Brush::m_tip_angle_smooth); init_slider(m_tip_mix, "tip-mix", pp::app::BrushStrokeFloatSetting::tip_mix, &Brush::m_tip_mix); init_slider(m_tip_wet, "tip-wet", pp::app::BrushStrokeFloatSetting::tip_wet, &Brush::m_tip_wet); init_slider(m_tip_noise, "tip-noise", pp::app::BrushStrokeFloatSetting::tip_noise, &Brush::m_tip_noise); init_slider(m_tip_hue, "tip-hue", pp::app::BrushStrokeFloatSetting::tip_hue, &Brush::m_tip_hue); init_slider(m_tip_sat, "tip-sat", pp::app::BrushStrokeFloatSetting::tip_saturation, &Brush::m_tip_sat); init_slider(m_tip_val, "tip-val", pp::app::BrushStrokeFloatSetting::tip_value, &Brush::m_tip_val); init_slider(m_jitter_scale, "jitter-scale", pp::app::BrushStrokeFloatSetting::jitter_scale, &Brush::m_jitter_scale); init_slider(m_jitter_angle, "jitter-angle", pp::app::BrushStrokeFloatSetting::jitter_angle, &Brush::m_jitter_angle); init_slider(m_jitter_scatter, "jitter-scatter", pp::app::BrushStrokeFloatSetting::jitter_scatter, &Brush::m_jitter_scatter); init_slider(m_jitter_flow, "jitter-flow", pp::app::BrushStrokeFloatSetting::jitter_flow, &Brush::m_jitter_flow); init_slider(m_jitter_opacity, "jitter-opacity", pp::app::BrushStrokeFloatSetting::jitter_opacity, &Brush::m_jitter_opacity); init_slider(m_jitter_hue, "jitter-hue", pp::app::BrushStrokeFloatSetting::jitter_hue, &Brush::m_jitter_hue); init_slider(m_jitter_sat, "jitter-sat", pp::app::BrushStrokeFloatSetting::jitter_saturation, &Brush::m_jitter_sat); init_slider(m_jitter_val, "jitter-val", pp::app::BrushStrokeFloatSetting::jitter_value, &Brush::m_jitter_val); init_slider(m_jitter_aspect, "jitter-aspect", pp::app::BrushStrokeFloatSetting::jitter_aspect, &Brush::m_jitter_aspect); init_checkbox(m_tip_angle_init, "tip-angle-init", pp::app::BrushStrokeBoolSetting::tip_angle_init, &Brush::m_tip_angle_init); init_checkbox(m_tip_angle_follow, "tip-angle-follow", pp::app::BrushStrokeBoolSetting::tip_angle_follow, &Brush::m_tip_angle_follow); init_checkbox(m_tip_flow_pressure, "tip-flow-pressure", pp::app::BrushStrokeBoolSetting::tip_flow_pressure, &Brush::m_tip_flow_pressure); init_checkbox(m_tip_opacity_pressure, "tip-opacity-pressure", pp::app::BrushStrokeBoolSetting::tip_opacity_pressure, &Brush::m_tip_opacity_pressure); init_checkbox(m_tip_size_pressure, "tip-size-pressure", pp::app::BrushStrokeBoolSetting::tip_size_pressure, &Brush::m_tip_size_pressure); init_checkbox(m_jitter_scatter_bothaxis, "jitter-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_scatter_both_axis, &Brush::m_jitter_scatter_bothaxis); init_checkbox(m_jitter_aspect_bothaxis, "jitter-aspect-bothaxis", pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis, &Brush::m_jitter_aspect_bothaxis); init_checkbox(m_jitter_hsv_eachsample, "jitter-hsv-eachsample", pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample, &Brush::m_jitter_hsv_eachsample); init_checkbox(m_tip_invert, "tip-invert", pp::app::BrushStrokeBoolSetting::tip_invert, &Brush::m_tip_invert); init_checkbox(m_tip_flipx, "tip-flipx", pp::app::BrushStrokeBoolSetting::tip_flip_x, &Brush::m_tip_flipx); init_checkbox(m_tip_flipy, "tip-flipy", pp::app::BrushStrokeBoolSetting::tip_flip_y, &Brush::m_tip_flipy); init_checkbox(m_pattern_enabled, "pattern-enabled", pp::app::BrushStrokeBoolSetting::pattern_enabled, &Brush::m_pattern_enabled); init_checkbox(m_dual_enabled, "dual-enabled", pp::app::BrushStrokeBoolSetting::dual_enabled, &Brush::m_dual_enabled); init_checkbox(m_dual_scatter_bothaxis, "dual-scatter-bothaxis", pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis, &Brush::m_dual_scatter_bothaxis); init_checkbox(m_dual_invert, "dual-invert", pp::app::BrushStrokeBoolSetting::dual_invert, &Brush::m_dual_invert); init_checkbox(m_dual_flipx, "dual-flipx", pp::app::BrushStrokeBoolSetting::dual_flip_x, &Brush::m_dual_flipx); init_checkbox(m_dual_flipy, "dual-flipy", pp::app::BrushStrokeBoolSetting::dual_flip_y, &Brush::m_dual_flipy); init_checkbox(m_dual_randflip, "dual-randflip", pp::app::BrushStrokeBoolSetting::dual_random_flip, &Brush::m_dual_randflip); init_checkbox(m_tip_randflipx, "tip-randflipx", pp::app::BrushStrokeBoolSetting::tip_random_flip_x, &Brush::m_tip_randflipx); init_checkbox(m_tip_randflipy, "tip-randflipy", pp::app::BrushStrokeBoolSetting::tip_random_flip_y, &Brush::m_tip_randflipy); init_checkbox(m_pattern_eachsample, "pattern-eachsample", pp::app::BrushStrokeBoolSetting::pattern_each_sample, &Brush::m_pattern_eachsample); init_checkbox(m_pattern_invert, "pattern-invert", pp::app::BrushStrokeBoolSetting::pattern_invert, &Brush::m_pattern_invert); init_checkbox(m_pattern_flipx, "pattern-flipx", pp::app::BrushStrokeBoolSetting::pattern_flip_x, &Brush::m_pattern_flipx); init_checkbox(m_pattern_flipy, "pattern-flipy", pp::app::BrushStrokeBoolSetting::pattern_flip_y, &Brush::m_pattern_flipy); init_checkbox(m_pattern_rand_offset, "pattern-rand-offset", pp::app::BrushStrokeBoolSetting::pattern_random_offset, &Brush::m_pattern_rand_offset); init_slider(m_dual_size, "dual-size", pp::app::BrushStrokeFloatSetting::dual_size, &Brush::m_dual_size); init_slider(m_dual_spacing, "dual-spacing", pp::app::BrushStrokeFloatSetting::dual_spacing, &Brush::m_dual_spacing); init_slider(m_dual_scatter, "dual-scatter", pp::app::BrushStrokeFloatSetting::dual_scatter, &Brush::m_dual_scatter); init_slider(m_tip_aspect, "tip-aspect", pp::app::BrushStrokeFloatSetting::tip_aspect, &Brush::m_tip_aspect); init_slider(m_dual_opacity, "dual-opacity", pp::app::BrushStrokeFloatSetting::dual_opacity, &Brush::m_dual_opacity); init_slider(m_dual_flow, "dual-flow", pp::app::BrushStrokeFloatSetting::dual_flow, &Brush::m_dual_flow); init_slider(m_dual_rotate, "dual-rotate", pp::app::BrushStrokeFloatSetting::dual_rotate, &Brush::m_dual_rotate); init_slider(m_pattern_scale, "pattern-scale", pp::app::BrushStrokeFloatSetting::pattern_scale, &Brush::m_pattern_scale); init_slider(m_pattern_brightness, "pattern-brightness", pp::app::BrushStrokeFloatSetting::pattern_brightness, &Brush::m_pattern_brightness); init_slider(m_pattern_contrast, "pattern-contrast", pp::app::BrushStrokeFloatSetting::pattern_contrast, &Brush::m_pattern_contrast); init_slider(m_pattern_depth, "pattern-depth", pp::app::BrushStrokeFloatSetting::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_mix { [](float v) { return glm::pow(v, 1.f / 10.f); }, [](float v) { return glm::pow(v, 10.f); }, }; SliderCurve curve_linear1k_perc { [](float v) { return v * 10.f; }, [](float v) { return v * 0.1f; }, }; 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_size1k_perc; m_curves[m_dual_spacing] = curve_size1k_perc; m_curves[m_dual_scatter] = curve_linear1k_perc; m_curves[m_dual_flow] = curve_quad; m_curves[m_pattern_scale] = curve_size1k_perc; m_curves[m_jitter_scatter] = curve_linear1k_perc; m_curves[m_tip_mix] = curve_mix; m_tip_aspect_reset = find("tip-aspect-reset"); m_tip_aspect_reset->on_click = [this](Node*) { execute_stroke_control_plan(pp::app::plan_brush_tip_aspect_reset()); }; m_dual_blend_mode = find("dual-blend-mode"); m_dual_blend_mode->on_select = [this](Node*, int index) { const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::dual, index); if (plan) { execute_stroke_control_plan(plan.value()); } }; m_pattern_blend_mode = find("pattern-blend-mode"); m_pattern_blend_mode->on_select = [this](Node*, int index) { const auto plan = pp::app::plan_brush_stroke_blend_mode(pp::app::BrushStrokeBlendSetting::pattern, index); if (plan) { execute_stroke_control_plan(plan.value()); } }; 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"); if (auto b = find("button-unfold-all")) { b->on_click = [this](Node*) { static bool visible = true; visible = !visible; find("fold-color")->SetVisibility(visible); find("fold-metrics")->SetVisibility(visible); find("fold-pattern")->SetVisibility(visible); find("fold-dualbrush")->SetVisibility(visible); find("fold-medium")->SetVisibility(visible); find("fold-colorvar")->SetVisibility(visible); find("fold-jitter")->SetVisibility(visible); }; } m_brush_settings_reset = find("brush-settings-reset"); m_brush_settings_reset->on_click = [this](Node*) { execute_stroke_control_plan(pp::app::plan_brush_default_settings_reset()); }; update_controls(); } void NodePanelStroke::init_slider( NodeSliderH*& target, const char* id, pp::app::BrushStrokeFloatSetting setting, float Brush::* prop) { target = find(id); target->on_value_changed = std::bind(&NodePanelStroke::handle_slide, this, setting, prop, std::placeholders::_1, std::placeholders::_2); //m_canvas->m_brush->*prop = target->m_values; } void NodePanelStroke::handle_slide( pp::app::BrushStrokeFloatSetting setting, float Brush::* prop, Node* target, float value) { auto curve = m_curves.find((NodeSliderH*)target); const auto brush_value = curve != m_curves.end() ? curve->second.to_value(value) : value; const auto plan = pp::app::plan_brush_stroke_float_setting(setting, brush_value); if (plan) { execute_stroke_control_plan(plan.value()); } else { Canvas::I->m_current_brush.get()->*prop = brush_value; } } void NodePanelStroke::init_checkbox( NodeCheckBox*& target, const char* id, pp::app::BrushStrokeBoolSetting setting, bool Brush::* prop) { target = find(id); target->on_value_changed = std::bind(&NodePanelStroke::handle_checkbox, this, setting, std::placeholders::_2); Canvas::I->m_current_brush.get()->*prop = target->checked; } void NodePanelStroke::handle_checkbox( pp::app::BrushStrokeBoolSetting setting, bool value) { const auto plan = pp::app::plan_brush_stroke_bool_setting(setting, value); execute_stroke_control_plan(plan); } void NodePanelStroke::execute_stroke_control_plan(const pp::app::BrushStrokeControlPlan& plan) { LegacyBrushStrokeControlServices services(*this); const auto status = pp::app::execute_brush_stroke_control_plan(plan, services); if (!status.ok()) { LOG("Brush stroke control action failed: %s", status.message); } } kEventResult NodePanelStroke::handle_event(Event* e) { switch (e->m_type) { case kEventType::MouseUpL: if (!m_mouse_inside) { mouse_release(); m_parent->remove_child(this); if (on_popup_close) on_popup_close(this); } break; default: return kEventResult::Available; break; } return kEventResult::Available; }