#include "pch.h" #include "app_core/brush_ui.h" #include "legacy_brush_ui_services.h" #include "legacy_ui_overlay_services.h" #include "log.h" #include "node_panel_stroke.h" #include "canvas.h" #include "node_button.h" #include "app.h" #include "abr.h" namespace { void add_stroke_float( pp::app::BrushStrokePanelInput& input, pp::app::BrushStrokeFloatSetting setting, float value) { input.float_values.push_back(pp::app::BrushStrokeFloatValue { .setting = setting, .value = value, }); } void add_stroke_bool( pp::app::BrushStrokePanelInput& input, pp::app::BrushStrokeBoolSetting setting, bool value) { input.bool_values.push_back(pp::app::BrushStrokeBoolValue { .setting = setting, .value = value, }); } void add_stroke_blend( pp::app::BrushStrokePanelInput& input, pp::app::BrushStrokeBlendSetting setting, int blend_mode) { input.blend_values.push_back(pp::app::BrushStrokeBlendValue { .setting = setting, .blend_mode = blend_mode, }); } pp::app::BrushStrokePanelInput make_stroke_panel_input(const Brush& brush) { pp::app::BrushStrokePanelInput input; input.float_values.reserve(29); input.bool_values.reserve(24); input.blend_values.reserve(3); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_size, brush.m_tip_size); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_spacing, brush.m_tip_spacing); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_flow, brush.m_tip_flow); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_opacity, brush.m_tip_opacity); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_angle, brush.m_tip_angle); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_angle_smooth, brush.m_tip_angle_smooth); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_mix, brush.m_tip_mix); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_wet, brush.m_tip_wet); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_noise, brush.m_tip_noise); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_scale, brush.m_jitter_scale); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_angle, brush.m_jitter_angle); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_scatter, brush.m_jitter_scatter); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_flow, brush.m_jitter_flow); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_opacity, brush.m_jitter_opacity); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_hue, brush.m_jitter_hue); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_saturation, brush.m_jitter_sat); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_value, brush.m_jitter_val); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::jitter_aspect, brush.m_jitter_aspect); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_size, brush.m_dual_size); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_spacing, brush.m_dual_spacing); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_flow, brush.m_dual_flow); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_scatter, brush.m_dual_scatter); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::tip_aspect, brush.m_tip_aspect); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_opacity, brush.m_dual_opacity); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::dual_rotate, brush.m_dual_rotate); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_scale, brush.m_pattern_scale); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_brightness, brush.m_pattern_brightness); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_contrast, brush.m_pattern_contrast); add_stroke_float(input, pp::app::BrushStrokeFloatSetting::pattern_depth, brush.m_pattern_depth); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample, brush.m_jitter_hsv_eachsample); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_angle_follow, brush.m_tip_angle_follow); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_angle_init, brush.m_tip_angle_init); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_flow_pressure, brush.m_tip_flow_pressure); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_opacity_pressure, brush.m_tip_opacity_pressure); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_size_pressure, brush.m_tip_size_pressure); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis, brush.m_jitter_aspect_bothaxis); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_invert, brush.m_tip_invert); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_flip_x, brush.m_tip_flipx); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_flip_y, brush.m_tip_flipy); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_enabled, brush.m_pattern_enabled); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_enabled, brush.m_dual_enabled); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis, brush.m_dual_scatter_bothaxis); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_invert, brush.m_dual_invert); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_flip_x, brush.m_dual_flipx); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_flip_y, brush.m_dual_flipy); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::dual_random_flip, brush.m_dual_randflip); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_random_flip_x, brush.m_tip_randflipx); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::tip_random_flip_y, brush.m_tip_randflipy); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_each_sample, brush.m_pattern_eachsample); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_invert, brush.m_pattern_invert); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_flip_x, brush.m_pattern_flipx); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_flip_y, brush.m_pattern_flipy); add_stroke_bool(input, pp::app::BrushStrokeBoolSetting::pattern_random_offset, brush.m_pattern_rand_offset); add_stroke_blend(input, pp::app::BrushStrokeBlendSetting::tip, brush.m_blend_mode); add_stroke_blend(input, pp::app::BrushStrokeBlendSetting::dual, brush.m_dual_blend_mode); add_stroke_blend(input, pp::app::BrushStrokeBlendSetting::pattern, brush.m_pattern_blend_mode); input.tip_thumbnail_path = brush.m_brush_thumb_path; input.dual_thumbnail_path = brush.m_dual_thumb_path; input.pattern_thumbnail_path = brush.m_pattern_thumb_path; return input; } } // 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() { if (!Canvas::I || !Canvas::I->m_current_brush) { return; } const auto view = pp::app::plan_brush_stroke_panel_view( make_stroke_panel_input(*Canvas::I->m_current_brush)); if (!view) { LOG("Brush stroke panel view failed: %s", view.status().message); return; } const auto set_slider = [this](NodeSliderH* slider, float value) { const auto curve = m_curves.find(slider); slider->m_value = curve != m_curves.end() ? curve->second.to_slider(value) : value; }; for (const auto& value : view.value().float_values) { switch (value.setting) { case pp::app::BrushStrokeFloatSetting::tip_size: set_slider(m_tip_size, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_spacing: set_slider(m_tip_spacing, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_flow: set_slider(m_tip_flow, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_opacity: set_slider(m_tip_opacity, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_angle: set_slider(m_tip_angle, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_angle_smooth: set_slider(m_tip_angle_smooth, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_mix: set_slider(m_tip_mix, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_wet: set_slider(m_tip_wet, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_noise: set_slider(m_tip_noise, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_scale: set_slider(m_jitter_scale, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_angle: set_slider(m_jitter_angle, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_scatter: set_slider(m_jitter_scatter, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_flow: set_slider(m_jitter_flow, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_opacity: set_slider(m_jitter_opacity, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_hue: set_slider(m_jitter_hue, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_saturation: set_slider(m_jitter_sat, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_value: set_slider(m_jitter_val, value.value); break; case pp::app::BrushStrokeFloatSetting::jitter_aspect: set_slider(m_jitter_aspect, value.value); break; case pp::app::BrushStrokeFloatSetting::dual_size: set_slider(m_dual_size, value.value); break; case pp::app::BrushStrokeFloatSetting::dual_spacing: set_slider(m_dual_spacing, value.value); break; case pp::app::BrushStrokeFloatSetting::dual_flow: set_slider(m_dual_flow, value.value); break; case pp::app::BrushStrokeFloatSetting::dual_scatter: set_slider(m_dual_scatter, value.value); break; case pp::app::BrushStrokeFloatSetting::tip_aspect: set_slider(m_tip_aspect, value.value); break; case pp::app::BrushStrokeFloatSetting::dual_opacity: set_slider(m_dual_opacity, value.value); break; case pp::app::BrushStrokeFloatSetting::dual_rotate: set_slider(m_dual_rotate, value.value); break; case pp::app::BrushStrokeFloatSetting::pattern_scale: set_slider(m_pattern_scale, value.value); break; case pp::app::BrushStrokeFloatSetting::pattern_brightness: set_slider(m_pattern_brightness, value.value); break; case pp::app::BrushStrokeFloatSetting::pattern_contrast: set_slider(m_pattern_contrast, value.value); break; case pp::app::BrushStrokeFloatSetting::pattern_depth: set_slider(m_pattern_depth, value.value); break; default: break; } } for (const auto& value : view.value().bool_values) { switch (value.setting) { case pp::app::BrushStrokeBoolSetting::jitter_hsv_each_sample: m_jitter_hsv_eachsample->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_angle_follow: m_tip_angle_follow->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_angle_init: m_tip_angle_init->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_flow_pressure: m_tip_flow_pressure->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_opacity_pressure: m_tip_opacity_pressure->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_size_pressure: m_tip_size_pressure->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::jitter_aspect_both_axis: m_jitter_aspect_bothaxis->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_invert: m_tip_invert->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_flip_x: m_tip_flipx->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_flip_y: m_tip_flipy->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::pattern_enabled: m_pattern_enabled->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::dual_enabled: m_dual_enabled->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::dual_scatter_both_axis: m_dual_scatter_bothaxis->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::dual_invert: m_dual_invert->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::dual_flip_x: m_dual_flipx->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::dual_flip_y: m_dual_flipy->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::dual_random_flip: m_dual_randflip->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_random_flip_x: m_tip_randflipx->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::tip_random_flip_y: m_tip_randflipy->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::pattern_each_sample: m_pattern_eachsample->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::pattern_invert: m_pattern_invert->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::pattern_flip_x: m_pattern_flipx->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::pattern_flip_y: m_pattern_flipy->set_value(value.value); break; case pp::app::BrushStrokeBoolSetting::pattern_random_offset: m_pattern_rand_offset->set_value(value.value); break; default: break; } } for (const auto& value : view.value().blend_values) { switch (value.setting) { case pp::app::BrushStrokeBlendSetting::tip: m_blend_mode->set_index(value.blend_mode); break; case pp::app::BrushStrokeBlendSetting::dual: m_dual_blend_mode->set_index(value.blend_mode); break; case pp::app::BrushStrokeBlendSetting::pattern: m_pattern_blend_mode->set_index(value.blend_mode); break; } } if (view.value().updates_preview) { m_preview->m_brush = Canvas::I->m_current_brush; m_preview->draw_stroke(); } if (m_brush_thumb->m_path != view.value().tip_thumbnail_path) m_brush_thumb->set_image(view.value().tip_thumbnail_path); if (m_dual_brush_thumb->m_path != view.value().dual_thumbnail_path) m_dual_brush_thumb->set_image(view.value().dual_thumbnail_path); if (m_pattern_thumb->m_path != view.value().pattern_thumbnail_path) m_pattern_thumb->set_image(view.value().pattern_thumbnail_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); pp::panopainter::configure_legacy_popup_overlay(*m_brush_popup); 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); pp::panopainter::configure_legacy_popup_overlay(*m_pattern_popup); //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 default_brush = std::make_shared(); default_brush->load_tip(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx)); //default_brush->load_dual(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx)); //default_brush->load_pattern(m_pattern_popup->get_texture_path(0), m_pattern_popup->get_thumb_path(0)); default_brush->m_tip_size = 30; default_brush->m_tip_flow = .9f; default_brush->m_tip_spacing = .1f; default_brush->m_tip_opacity = 1.f; Canvas::I->m_current_brush = default_brush; // 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); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, App::I->presets); auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor(*this); 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"); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, tick); 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); pp::panopainter::activate_legacy_popup_overlay(*App::I->presets); root()->update(); pp::panopainter::bind_legacy_popup_close_destroys_overlay(*App::I->presets, tick); 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); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, m_brush_popup); auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor(*this); 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"); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, tick); 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); pp::panopainter::activate_legacy_popup_overlay(*m_brush_popup); root()->update(); pp::panopainter::bind_legacy_popup_close_destroys_overlay(*m_brush_popup, tick); 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); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, m_brush_popup); auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor(*this); 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"); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, tick); 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); pp::panopainter::activate_legacy_popup_overlay(*m_brush_popup); root()->update(); pp::panopainter::bind_legacy_popup_close_destroys_overlay(*m_brush_popup, tick); 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); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, m_pattern_popup); auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor(*this); 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"); (void)pp::panopainter::attach_legacy_overlay_node_to_root(*this, tick); 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); pp::panopainter::activate_legacy_popup_overlay(*m_pattern_popup); root()->update(); pp::panopainter::bind_legacy_popup_close_destroys_overlay(*m_pattern_popup, tick); 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) { const auto status = pp::panopainter::execute_legacy_brush_stroke_control_plan(*this, plan); 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) { pp::panopainter::close_legacy_popup_panel(*this, on_popup_close); } break; default: return kEventResult::Available; break; } return kEventResult::Available; }