Load white/gray icons by icon manager

This commit is contained in:
Filip Sykala - NTB T15p 2023-06-30 20:30:51 +02:00
parent 9ee86599d5
commit 3a6c85ef58
4 changed files with 349 additions and 83 deletions

View File

@ -112,6 +112,20 @@ std::string volume_name(const EmbossShape& shape);
/// <returns>Params</returns>
CreateVolumeParams create_input(GLCanvas3D &canvas, RaycastManager &raycaster, ModelVolumeType volume_type);
enum class IconType : unsigned {
reset_value,
reset_value_hover,
lock,
lock_hover,
unlock,
unlock_hover,
// automatic calc of icon's count
_count
};
const IconManager::Icon &get_icon(const IconManager::Icons &icons, IconType type) {
return *icons[static_cast<unsigned>(type)]; }
// This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again,
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
@ -121,6 +135,9 @@ struct GuiCfg
double screen_scale;
float main_toolbar_height;
// Define bigger size(width or height)
unsigned texture_max_size_px = 64;
// Zero means it is calculated in init function
ImVec2 minimal_window_size = ImVec2(0, 0);
@ -337,6 +354,55 @@ void GLGizmoSVG::on_unregister_raycasters_for_picking(){
m_rotate_gizmo.unregister_raycasters_for_picking();
}
namespace{
IconManager::Icons init_icons(IconManager &mng, const GuiCfg &cfg)
{
mng.release();
ImVec2 size(cfg.icon_width, cfg.icon_width);
// icon order has to match the enum IconType
IconManager::InitTypes init_types{
{"undo.svg", size, IconManager::RasterType::white_only_data}, // undo
{"undo.svg", size, IconManager::RasterType::color}, // undo_hovered
{"lock_closed.svg", size, IconManager::RasterType::white_only_data}, // lock,
{"lock_closed_f.svg",size, IconManager::RasterType::white_only_data}, // lock_hovered,
{"lock_open.svg", size, IconManager::RasterType::white_only_data}, // unlock,
{"lock_open_f.svg", size, IconManager::RasterType::white_only_data} // unlock_hovered
};
assert(init_types.size() == static_cast<size_t>(IconType::_count));
std::string path = resources_dir() + "/icons/";
for (IconManager::InitType &init_type : init_types)
init_type.filepath = path + init_type.filepath;
return mng.init(init_types);
//IconManager::VIcons vicons = mng.init(init_types);
//
//// flatten icons
//IconManager::Icons icons;
//icons.reserve(vicons.size());
//for (IconManager::Icons &i : vicons)
// icons.push_back(i.front());
//return icons;
}
bool reset_button(const IconManager::Icons &icons)
{
float reset_offset = ImGui::GetStyle().FramePadding.x;
ImGui::SameLine(reset_offset);
// from GLGizmoCut
//std::string label_id = "neco";
//std::string btn_label;
//btn_label += ImGui::RevertButton;
//return ImGui::Button((btn_label + "##" + label_id).c_str());
return clickable(get_icon(icons, IconType::reset_value), get_icon(icons, IconType::reset_value_hover));
}
} // namespace
void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit)
{
set_volume_by_selection();
@ -358,6 +424,8 @@ void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit)
// set position near toolbar
m_set_window_offset = ImVec2(-1.f, -1.f);
m_icons = init_icons(m_icon_manager, *m_gui_cfg); // need regeneration when change resolution(move between monitors)
}
const ImVec2 &min_window_size = m_gui_cfg->minimal_window_size;
@ -399,8 +467,8 @@ void GLGizmoSVG::on_render_input_window(float x, float y, float bottom_limit)
}
ImGui::End();
if (!is_opened)
close();
//if (!is_opened)
// close();
}
void GLGizmoSVG::on_set_state()
@ -455,7 +523,8 @@ void GLGizmoSVG::on_dragging(const UpdateData &data) { m_rotate_gizmo.dragging(d
#include "slic3r/GUI/BitmapCache.hpp"
#include "nanosvg/nanosvgrast.h"
namespace{
bool init_texture(Texture &texture, const ModelVolume &mv) {
bool init_texture(Texture &texture, const ModelVolume &mv, unsigned max_size_px)
{
if (!mv.emboss_shape.has_value())
return false;
@ -464,7 +533,6 @@ bool init_texture(Texture &texture, const ModelVolume &mv) {
if (filepath.empty())
return false;
unsigned max_size_px = 256;
// inspired by:
// GLTexture::load_from_svg_file(filepath, false, false, false, max_size_px);
NSVGimage *image = BitmapCache::nsvgParseFromFileWithReplace(filepath.c_str(), "px", 96.0f, {});
@ -563,7 +631,7 @@ void GLGizmoSVG::set_volume_by_selection()
// Calculate current angle of up vector
m_angle = calc_up(gl_volume->world_matrix(), Slic3r::GUI::up_limit);
m_distance = calc_distance(*gl_volume, m_raycast_manager, m_parent);
init_texture(m_texture, *m_volume);
// calculate scale for height and depth inside of scaled object instance
calculate_scale();
}
@ -644,15 +712,9 @@ void GLGizmoSVG::draw_window()
ImGui::Text("Not valid state please report reproduction steps on github");
return;
}
draw_preview();
if (m_volume->emboss_shape.has_value())
ImGui::Text("SVG file path is %s", m_volume->emboss_shape->svg_file_path.c_str());
if (m_texture.id != 0) {
ImTextureID id = (void *) static_cast<intptr_t>(m_texture.id);
ImVec2 s(m_texture.width, m_texture.height);
ImGui::Image(id, s);
}
ImGui::Separator();
ImGui::Indent(m_gui_cfg->icon_width);
draw_depth();
@ -663,19 +725,46 @@ void GLGizmoSVG::draw_window()
draw_rotation();
ImGui::Unindent(m_gui_cfg->icon_width);
if (ImGui::Button("change file")) {
m_volume_shape.shapes_with_ids = select_shape().shapes_with_ids;
init_texture(m_texture, *m_volume);
// TODO: use setted scale
process();
}
if (!m_volume->is_the_only_one_part()) {
ImGui::Separator();
draw_model_type();
}
}
void GLGizmoSVG::draw_preview(){
if (m_volume->emboss_shape.has_value())
ImGui::Text("SVG file path is %s", m_volume->emboss_shape->svg_file_path.c_str());
if (m_texture.id == 0)
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
if (m_texture.id != 0) {
ImTextureID id = (void *) static_cast<intptr_t>(m_texture.id);
ImVec2 s(m_texture.width, m_texture.height);
ImGui::Image(id, s);
}
ImGui::SameLine();
if (ImGui::Button("change file")) {
m_volume_shape.shapes_with_ids = select_shape().shapes_with_ids;
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
process();
}
// Re-Load button
bool can_reload = !m_volume_shape.svg_file_path.empty();
if (can_reload) {
ImGui::SameLine();
if (clickable(get_icon(m_icons, IconType::reset_value), get_icon(m_icons, IconType::reset_value_hover))) {
m_volume_shape.shapes_with_ids = select_shape(m_volume_shape.svg_file_path).shapes_with_ids;
init_texture(m_texture, *m_volume, m_gui_cfg->texture_max_size_px);
process();
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Re-load SVG file from disk.").c_str());
}
}
void GLGizmoSVG::draw_depth()
{
ImGuiWrapper::text(m_gui_cfg->translations.depth);
@ -746,16 +835,13 @@ void GLGizmoSVG::draw_size()
ImGui::SetTooltip("%s", _u8L("Height of SVG.").c_str());
bool can_reset = !is_approx(m_volume_shape.scale, DEFAULT_SCALE);
m_imgui->disabled_begin(!can_reset);
ScopeGuard sc([imgui = m_imgui]() { imgui->disabled_end(); });
float reset_offset = ImGui::GetStyle().FramePadding.x;
ImGui::SameLine(reset_offset);
if (ImGui::Button("R##size_reset")) {
if (can_reset) {
if (reset_button(m_icons)) {
m_volume_shape.scale = DEFAULT_SCALE;
process();
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Reset scale to loaded one from the SVG").c_str());
}
}
void GLGizmoSVG::draw_use_surface()
@ -810,16 +896,14 @@ void GLGizmoSVG::draw_distance()
is_moved = true;
}
m_imgui->disabled_begin(!m_distance.has_value() && allowe_surface_distance);
ScopeGuard sg2([imgui = m_imgui]() { imgui->disabled_end(); });
float reset_offset = ImGui::GetStyle().FramePadding.x;
ImGui::SameLine(reset_offset);
if (ImGui::Button("R##distance_reset")){
bool can_reset = m_distance.has_value();
if (can_reset) {
if (reset_button(m_icons)) {
m_distance.reset();
is_moved = true;
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Reset distance to zero value").c_str());
}
if (is_moved)
do_local_z_move(m_parent, m_distance.value_or(.0f) - prev_distance);
@ -837,7 +921,7 @@ void GLGizmoSVG::draw_rotation()
// minus create clock-wise roation from CCW
float angle = m_angle.value_or(0.f);
float angle_deg = static_cast<float>(-angle * 180 / M_PI);
if (m_imgui->slider_float("##angle", &angle_deg, limits.angle.min, limits.angle.max, u8"%.2f DEG", 1.f, false, _L("Rotate text Clock-wise."))){
if (m_imgui->slider_float("##angle", &angle_deg, limits.angle.min, limits.angle.max, u8"%.2f °", 1.f, false, _L("Rotate text Clock-wise."))){
// convert back to radians and CCW
double angle_rad = -angle_deg * M_PI / 180.0;
Geometry::to_range_pi_pi(angle_rad);
@ -854,24 +938,9 @@ void GLGizmoSVG::draw_rotation()
process();
}
if (!m_volume->is_the_only_one_part()) {
// Keep up - lock button icon
ImGui::SameLine(m_gui_cfg->lock_offset);
ImGui::Checkbox("##Lock_up_vector", &m_keep_up);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", (m_keep_up ?
_u8L("Unlock the rotation when moving volume along the object's surface.") :
_u8L("Lock the rotation when moving volume along the object's surface."))
.c_str());
}
// Reset button
m_imgui->disabled_begin(!m_angle.has_value());
ScopeGuard sg([imgui = m_imgui]() { imgui->disabled_end(); });
float reset_offset = ImGui::GetStyle().FramePadding.x;
ImGui::SameLine(reset_offset);
if (ImGui::Button("R##angle_reset")) {
if (m_angle.has_value()) {
if (reset_button(m_icons)) {
do_local_z_rotate(m_parent, -(*m_angle));
m_angle.reset();
@ -880,19 +949,21 @@ void GLGizmoSVG::draw_rotation()
process();
} else if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", _u8L("Reset rotation to zero value").c_str());
}
// Keep up - lock button icon
//ImGui::SameLine(m_gui_cfg->lock_offset);
//const IconManager::Icon &icon = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::activable);
//const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_up ? IconType::lock_bold : IconType::unlock_bold, IconState::activable);
//const IconManager::Icon &icon_disable = get_icon(m_icons, m_keep_up ? IconType::lock : IconType::unlock, IconState::disabled);
//if (button(icon, icon_hover, icon_disable))
// m_keep_up = !m_keep_up;
//if (ImGui::IsItemHovered())
// ImGui::SetTooltip("%s", (m_keep_up?
// _u8L("Unlock the text's rotation when moving text along the object's surface."):
// _u8L("Lock the text's rotation when moving text along the object's surface.")
// ).c_str());
if (!m_volume->is_the_only_one_part()) {
ImGui::SameLine(m_gui_cfg->lock_offset);
const IconManager::Icon &icon = get_icon(m_icons,m_keep_up ? IconType::lock : IconType::unlock);
const IconManager::Icon &icon_hover = get_icon(m_icons, m_keep_up ? IconType::lock_hover : IconType::unlock_hover);
if (button(icon, icon_hover, icon))
m_keep_up = !m_keep_up;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", (m_keep_up?
_u8L("Free angle when dragging above the object's surface."):
_u8L("Keep same rotation angle when dragging above the object's surface.")
).c_str());
}
}
void GLGizmoSVG::draw_model_type()

View File

@ -8,6 +8,7 @@
#include "slic3r/GUI/SurfaceDrag.hpp"
#include "slic3r/GUI/GLTexture.hpp"
#include "slic3r/Utils/RaycastManager.hpp"
#include "slic3r/GUI/IconManager.hpp"
#include <optional>
#include <memory>
@ -115,6 +116,7 @@ private:
bool process();
void close();
void draw_window();
void draw_preview();
void draw_depth();
void draw_size();
void draw_use_surface();
@ -172,6 +174,9 @@ private:
Texture m_texture;
IconManager m_icon_manager;
IconManager::Icons m_icons;
// only temporary solution
static const std::string M_ICON_FILENAME;
};

View File

@ -1,6 +1,15 @@
#include "IconManager.hpp"
#include <cmath>
#include <boost/log/trivial.hpp>
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/Utils.hpp" // ScopeGuard
#include "3DScene.hpp" // glsafe
#include "GL/glew.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "imgui/imstb_rectpack.h" // distribute rectangles
using namespace Slic3r::GUI;
@ -14,12 +23,188 @@ static void draw_transparent_icon(const IconManager::Icon &icon); // only help f
IconManager::~IconManager() {
priv::clear(m_icons);
// release opengl texture is made in ~GLTexture()
if (m_id != 0)
glsafe(::glDeleteTextures(1, &m_id));
}
std::vector<IconManager::Icons> IconManager::init(const InitTypes &input)
namespace {
NSVGimage *parse_file(const char * filepath) {
FILE *fp = boost::nowide::fopen(filepath, "rb");
assert(fp != nullptr);
if (fp == nullptr)
return nullptr;
Slic3r::ScopeGuard sg([fp]() { fclose(fp); });
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// Note: +1 is for null termination
auto data_ptr = std::make_unique<char[]>(size+1);
data_ptr[size] = '\0'; // Must be null terminated.
size_t readed_size = fread(data_ptr.get(), 1, size, fp);
assert(readed_size == size);
if (readed_size != size)
return nullptr;
return nsvgParse(data_ptr.get(), "px", 96.0f);
}
void subdata(unsigned char *data, size_t data_stride, const std::vector<unsigned char> &data2, size_t data2_row) {
assert(data_stride >= data2_row);
for (size_t data2_offset = 0, data_offset = 0;
data2_offset < data2.size();
data2_offset += data2_row, data_offset += data_stride)
::memcpy((void *)(data + data_offset), (const void *)(data2.data() + data2_offset), data2_row);
}
}
IconManager::Icons IconManager::init(const InitTypes &input)
{
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
if (input.empty())
return {};
// TODO: remove in future
if (m_id != 0) {
glsafe(::glDeleteTextures(1, &m_id));
m_id = 0;
}
int total_surface = 0;
for (const InitType &i : input)
total_surface += i.size.x * i.size.y;
const int surface_sqrt = (int)sqrt((float)total_surface) + 1;
// Start packing
// Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
const int TEX_HEIGHT_MAX = 1024 * 32;
int width = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512;
int num_nodes = width;
std::vector<stbrp_node> nodes(num_nodes);
stbrp_context context;
stbrp_init_target(&context, width, TEX_HEIGHT_MAX, nodes.data(), num_nodes);
ImVector<stbrp_rect> pack_rects;
pack_rects.resize(input.size());
memset(pack_rects.Data, 0, (size_t) pack_rects.size_in_bytes());
for (int i = 0; i < input.size(); i++) {
const ImVec2 &size = input[i].size;
assert(size.x > 1);
assert(size.y > 1);
pack_rects[i].w = size.x;
pack_rects[i].h = size.y;
}
int pack_rects_res = stbrp_pack_rects(&context, &pack_rects[0], pack_rects.Size);
assert(pack_rects_res == 1);
if (pack_rects_res != 1)
return {};
ImVec2 tex_size(width, width);
for (const stbrp_rect &rect : pack_rects) {
float x = rect.x + rect.w;
float y = rect.y + rect.h;
if(x > tex_size.x) tex_size.x = x;
if(y > tex_size.y) tex_size.y = y;
}
Icons result(input.size());
for (int i = 0; i < pack_rects.Size; i++) {
const stbrp_rect &rect = pack_rects[i];
assert(rect.was_packed);
if (!rect.was_packed)
return {};
ImVec2 tl(rect.x / tex_size.x, rect.y / tex_size.y);
ImVec2 br((rect.x + rect.w) / tex_size.x, (rect.y + rect.h) / tex_size.y);
assert(input[i].size.x == rect.w);
assert(input[i].size.y == rect.h);
Icon icon = {input[i].size, tl, br};
result[i] = std::make_shared<Icon>(std::move(icon));
}
NSVGrasterizer *rast = nsvgCreateRasterizer();
assert(rast != nullptr);
if (rast == nullptr)
return {};
ScopeGuard sg_rast([rast]() { ::nsvgDeleteRasterizer(rast); });
int channels = 4;
int n_pixels = tex_size.x * tex_size.y;
// store data for whole texture
std::vector<unsigned char> data(n_pixels * channels, {0});
// initialize original index locations
std::vector<size_t> idx(input.size());
std::iota(idx.begin(), idx.end(), 0);
// Group same filename by sort inputs
// sort indexes based on comparing values in input
std::sort(idx.begin(), idx.end(), [&input](size_t i1, size_t i2) { return input[i1].filepath < input[i2].filepath; });
for (size_t j: idx) {
const InitType &i = input[j];
if (i.filepath.empty())
continue; // no file path only reservation of space for texture
if (!boost::filesystem::exists(i.filepath))
continue;
if (!boost::algorithm::iends_with(i.filepath, ".svg"))
continue;
NSVGimage *image = parse_file(i.filepath.c_str());
assert(image != nullptr);
if (image == nullptr)
return {};
ScopeGuard sg_image([image]() { ::nsvgDelete(image); });
float svg_scale = i.size.y / image->height;
// scale should be same in both directions
assert(is_approx(svg_scale, i.size.y / image->width));
const stbrp_rect &rect = pack_rects[j];
int n_pixels = rect.w * rect.h;
std::vector<unsigned char> icon_data(n_pixels * channels, {0});
::nsvgRasterize(rast, image, 0, 0, svg_scale, icon_data.data(), i.size.x, i.size.y, i.size.x * channels);
// makes white or gray only data in icon
if (i.type == RasterType::white_only_data ||
i.type == RasterType::gray_only_data) {
unsigned char value = (i.type == RasterType::white_only_data) ? 255 : 127;
for (size_t k = 0; k < icon_data.size(); k += channels)
if (icon_data[k] != 0 || icon_data[k + 1] != 0 || icon_data[k + 2] != 0) {
icon_data[k] = value;
icon_data[k + 1] = value;
icon_data[k + 2] = value;
}
}
int start_offset = (rect.y*tex_size.x + rect.x) * channels;
int data_stride = tex_size.x * channels;
subdata(data.data() + start_offset, data_stride, icon_data, rect.w * channels);
}
if (m_id != 0)
glsafe(::glDeleteTextures(1, &m_id));
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint) m_id));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei) tex_size.x, (GLsizei) tex_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*) data.data()));
// bind no texture
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
for (const auto &i : result)
i->tex_id = m_id;
return result;
}
std::vector<IconManager::Icons> IconManager::init(const std::vector<std::string> &file_paths, const ImVec2 &size, RasterType type)

View File

@ -70,10 +70,10 @@ public:
/// Initialize raster texture on GPU with given images
/// NOTE: Have to be called after OpenGL initialization
/// </summary>
/// <param name="input">Define files and its </param>
/// <param name="input">Define files and its size with rasterization</param>
/// <returns>Rasterized icons stored on GPU,
/// Same size and order as input, each item of vector is set of texture in order by RasterType</returns>
VIcons init(const InitTypes &input);
Icons init(const InitTypes &input);
/// <summary>
/// Initialize multiple icons with same settings for size and type
@ -96,6 +96,11 @@ public:
private:
// keep data stored on GPU
GLTexture m_icons_texture;
unsigned int m_id{ 0 };
int m_width{ 0 };
int m_height{ 0 };
Icons m_icons;
};