|
|
|
@@ -494,7 +494,17 @@ bool GLGizmoTextureDisplacement::on_init()
|
|
|
|
|
{ctrl + _L("Mouse wheel"), m_desc["gap_area"]},
|
|
|
|
|
toggle_wireframe_shortcut
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Both ways into the same input box on any slider in the panel: ImGui turns a slider into one when
|
|
|
|
|
// it is Ctrl+clicked or when it is given the keyboard focus, which is what the edit icon asks for.
|
|
|
|
|
m_desc["type_value"] = _L("Type an exact value into a slider");
|
|
|
|
|
m_shortcuts_sliders = {
|
|
|
|
|
{ctrl + _L("Left mouse button"), m_desc["type_value"]},
|
|
|
|
|
{_L("Edit icon at a slider's right end"), m_desc["type_value"]},
|
|
|
|
|
};
|
|
|
|
|
for (std::vector<std::pair<wxString, wxString>> *list : {&m_shortcuts_brush, &m_shortcuts_bucket_fill, &m_shortcuts_gap_fill})
|
|
|
|
|
list->insert(list->end(), m_shortcuts_sliders.begin(), m_shortcuts_sliders.end());
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -3680,6 +3690,7 @@ void GLGizmoTextureDisplacement::ensure_panel_icons()
|
|
|
|
|
"texture_displacement_adjust.svg", "canvas_drag.svg", "texture_displacement_move_up.svg",
|
|
|
|
|
"texture_displacement_move_down.svg", "texture_displacement_drag.svg",
|
|
|
|
|
"texture_displacement_select_all.svg", "texture_displacement_erase_all.svg",
|
|
|
|
|
"edit.svg", // the "type a value" button at the right end of every slider
|
|
|
|
|
};
|
|
|
|
|
std::vector<std::string> paths;
|
|
|
|
|
paths.reserve(names.size());
|
|
|
|
@@ -5919,8 +5930,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|
|
|
|
}
|
|
|
|
|
return s + "...";
|
|
|
|
|
};
|
|
|
|
|
// Label column + a slider filling the rest of the row, value printed inside the track. Ctrl+click on
|
|
|
|
|
// any of these types a value in. `right_inset` keeps a layer card's inner padding.
|
|
|
|
|
// Label column + a slider filling the rest of the row, value printed inside the track.
|
|
|
|
|
// `right_inset` keeps a layer card's inner padding.
|
|
|
|
|
const auto slider_label = [&](const wxString &label) {
|
|
|
|
|
const float x0 = ImGui::GetCursorPosX();
|
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
|
|
@@ -5928,17 +5939,98 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ---- Typed values on sliders ----
|
|
|
|
|
// Every slider in the panel goes through typed_row(), so all of them take a number the same two
|
|
|
|
|
// ways: Ctrl+click on the track, or the edit icon at the right end for the same box without the
|
|
|
|
|
// modifier. Both end up in ImGui's own "temp input" mode - the icon only asks ImGui to focus the
|
|
|
|
|
// slider, which is what turns it into an input box (see SliderScalar) - so the parsing, the
|
|
|
|
|
// clamping and the Enter/click-away behaviour are ImGui's, not a second implementation.
|
|
|
|
|
|
|
|
|
|
// Retires a click whose row is no longer being drawn - the panel changed mode in between - so that
|
|
|
|
|
// it cannot fire later when that row comes back. Before any row is drawn, so a live one still wins.
|
|
|
|
|
if (!m_slider_type_request.empty() && ImGui::GetFrameCount() > m_slider_type_request_frame + 1)
|
|
|
|
|
m_slider_type_request.clear();
|
|
|
|
|
|
|
|
|
|
// Exactly as tall as the slider it sits beside, so it lines up with the track without the row
|
|
|
|
|
// having to be re-centred around it; icon_button() pads the glyph itself down to about 0.7 of this.
|
|
|
|
|
const float edit_icon_w = frame_h;
|
|
|
|
|
// What the slider's own width has to give up for the icon beside it.
|
|
|
|
|
const float edit_icon_room = edit_icon_w + gap_s;
|
|
|
|
|
|
|
|
|
|
// One end of a range, through the slider's own format, so the bounds in the tooltip read exactly
|
|
|
|
|
// like the value in the track. A parenthesised note in the format - "(auto)" - is dropped: it
|
|
|
|
|
// describes the current state, not the bound.
|
|
|
|
|
const auto fmt_bound = [](const char *format, auto v) {
|
|
|
|
|
char buf[64];
|
|
|
|
|
std::snprintf(buf, sizeof(buf), format, v);
|
|
|
|
|
std::string out(buf);
|
|
|
|
|
const size_t paren = out.find('(');
|
|
|
|
|
if (paren != std::string::npos)
|
|
|
|
|
out.erase(paren);
|
|
|
|
|
while (!out.empty() && out.back() == ' ')
|
|
|
|
|
out.pop_back();
|
|
|
|
|
return out;
|
|
|
|
|
};
|
|
|
|
|
const auto edit_icon_tip = [](const std::string &lo, const std::string &hi) {
|
|
|
|
|
return _u8L("Type a value instead of dragging. Ctrl+click on the slider does the same.") + "\n\n" +
|
|
|
|
|
Slic3r::format(_u8L("Range: %1% to %2%"), lo, hi);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Draws the edit icon at the right end of the row and then `slider` to its left - in that order on
|
|
|
|
|
// purpose. The slider is then the last item submitted, so the hover_tip() a caller writes after the
|
|
|
|
|
// row still describes the slider rather than the icon, and no row has to pass its tooltip through
|
|
|
|
|
// here. `item_w` follows SetNextItemWidth's convention (negative measures back from the right edge)
|
|
|
|
|
// and already has edit_icon_room taken out of it.
|
|
|
|
|
const auto typed_row = [&](const char *id, float item_w, const std::string &tip,
|
|
|
|
|
const std::function<bool()> &slider) -> bool {
|
|
|
|
|
const float x0 = ImGui::GetCursorPosX();
|
|
|
|
|
const float w = item_w > 0.f ? item_w : std::max(1.f, ImGui::GetContentRegionAvail().x + item_w);
|
|
|
|
|
ImGui::SetCursorPosX(x0 + w + gap_s);
|
|
|
|
|
if (icon_button(int(ImGui::GetID(id)), "edit.svg", edit_icon_w, _L("Edit"), from_u8(tip))) {
|
|
|
|
|
m_slider_type_request = id;
|
|
|
|
|
m_slider_type_request_frame = ImGui::GetFrameCount();
|
|
|
|
|
m_parent.set_as_dirty(); // the next frame is where the request above is turned into focus
|
|
|
|
|
}
|
|
|
|
|
// Back onto the same row and to its start, which is where the slider belongs.
|
|
|
|
|
ImGui::SameLine(0.f, 0.f);
|
|
|
|
|
ImGui::SetCursorPosX(x0);
|
|
|
|
|
// Deferred from the click a frame ago: SetKeyboardFocusHere() is a request for the *next*
|
|
|
|
|
// frame, so it has to be issued before the slider it names, not from the icon beside it.
|
|
|
|
|
if (m_slider_type_request == id) {
|
|
|
|
|
ImGui::SetKeyboardFocusHere();
|
|
|
|
|
m_slider_type_request.clear();
|
|
|
|
|
m_parent.set_as_dirty(); // the focus only lands on a frame that actually gets drawn
|
|
|
|
|
}
|
|
|
|
|
ImGui::SetNextItemWidth(w);
|
|
|
|
|
return slider();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// The two forms every slider in the panel is one of. They derive the icon's range note from the
|
|
|
|
|
// bounds they are already given, so no row states its bounds or its format twice.
|
|
|
|
|
const auto typed_slider = [&](const char *id, float item_w, float *v, float v_min, float v_max,
|
|
|
|
|
const char *format, bool log) -> bool {
|
|
|
|
|
return typed_row(id, item_w, edit_icon_tip(fmt_bound(format, v_min), fmt_bound(format, v_max)), [&] {
|
|
|
|
|
return ImGui::SliderFloat(id, v, v_min, v_max, format,
|
|
|
|
|
ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
const auto typed_slider_int = [&](const char *id, float item_w, int *v, int v_min, int v_max,
|
|
|
|
|
const char *format) -> bool {
|
|
|
|
|
return typed_row(id, item_w, edit_icon_tip(fmt_bound(format, v_min), fmt_bound(format, v_max)), [&] {
|
|
|
|
|
return ImGui::SliderInt(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto float_row = [&](const char *id, const wxString &label, float *v, float v_min, float v_max, const char *format,
|
|
|
|
|
bool log, float right_inset) -> bool {
|
|
|
|
|
slider_label(label);
|
|
|
|
|
ImGui::SetNextItemWidth(-std::max(right_inset, 1.f));
|
|
|
|
|
return ImGui::SliderFloat(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
|
|
|
|
|
return typed_slider(id, -(std::max(right_inset, 1.f) + edit_icon_room), v, v_min, v_max, format, log);
|
|
|
|
|
};
|
|
|
|
|
const auto int_row = [&](const char *id, const wxString &label, int *v, int v_min, int v_max, const char *format,
|
|
|
|
|
float right_inset) -> bool {
|
|
|
|
|
slider_label(label);
|
|
|
|
|
ImGui::SetNextItemWidth(-std::max(right_inset, 1.f));
|
|
|
|
|
return ImGui::SliderInt(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp);
|
|
|
|
|
return typed_slider_int(id, -(std::max(right_inset, 1.f) + edit_icon_room), v, v_min, v_max, format);
|
|
|
|
|
};
|
|
|
|
|
const auto layer_name = [](const TextureDisplacementLayer &l) {
|
|
|
|
|
return l.name.empty() ? Slic3r::format(_u8L("Layer %1%"), l.slot + 1) : l.name;
|
|
|
|
@@ -6160,10 +6252,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|
|
|
|
const auto centre_on_row = [&](float h) { ImGui::SetCursorPosY(row_y + std::round((icon_md - h) * 0.5f)); };
|
|
|
|
|
vsep(icon_md);
|
|
|
|
|
if (is_brush_mode) {
|
|
|
|
|
ImGui::SetNextItemWidth(std::max(1.f, row_end - ImGui::GetCursorPosX() - (3.f * gap_s + 1.f + 2.f * icon_sm)));
|
|
|
|
|
centre_on_row(frame_h);
|
|
|
|
|
ImGui::SliderFloat("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f mm",
|
|
|
|
|
ImGuiSliderFlags_AlwaysClamp);
|
|
|
|
|
typed_slider("##cursor_radius",
|
|
|
|
|
std::max(1.f, row_end - ImGui::GetCursorPosX() - (3.f * gap_s + 1.f + 2.f * icon_sm) - edit_icon_room),
|
|
|
|
|
&m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f mm", false);
|
|
|
|
|
hover_tip(m_desc.at("cursor_size"));
|
|
|
|
|
vsep(icon_sm);
|
|
|
|
|
const bool is_circle = m_cursor_type == TriangleSelector::CursorType::CIRCLE;
|
|
|
|
@@ -6177,10 +6269,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|
|
|
|
_L("Sphere - paints only within a ball around the point under the cursor")))
|
|
|
|
|
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
|
|
|
|
} else if (is_area_mode) {
|
|
|
|
|
ImGui::SetNextItemWidth(std::max(1.f, row_end - ImGui::GetCursorPosX()));
|
|
|
|
|
centre_on_row(frame_h);
|
|
|
|
|
ImGui::SliderFloat("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, "%.0f°",
|
|
|
|
|
ImGuiSliderFlags_AlwaysClamp);
|
|
|
|
|
typed_slider("##smart_fill_angle", std::max(1.f, row_end - ImGui::GetCursorPosX() - edit_icon_room),
|
|
|
|
|
&m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, "%.0f°", false);
|
|
|
|
|
hover_tip(_u8L("Angle threshold - the fill stops at edges sharper than this"));
|
|
|
|
|
} else {
|
|
|
|
|
centre_on_row(frame_h);
|
|
|
|
@@ -7372,7 +7463,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|
|
|
|
: m_tool_type == ToolType::SMART_FILL ? m_shortcuts_bucket_fill
|
|
|
|
|
: m_tool_type == ToolType::BRUSH ? m_shortcuts_brush
|
|
|
|
|
: m_tool_type == ToolType::GAP_FILL ? m_shortcuts_gap_fill
|
|
|
|
|
: std::vector<std::pair<wxString, wxString>>{};
|
|
|
|
|
: m_shortcuts_sliders; // the sliders are there whether or not a painting tool is
|
|
|
|
|
|
|
|
|
|
ImGui::SetCursorPosY(row_y + (row_h - icon_h) * .5f); // center vertically
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(int(m_imgui->scaled(.5f)), style.ItemSpacing.y));
|
|
|
|
|