mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 16:21:14 +00:00
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes. clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer. * Remove Unused Project Includes From Files With Platform-Specific Code A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block. * Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass The platform-file pass treated pchheader.hpp as an ordinary header and emptied it, and left GUI_Preview.hpp and 14 other files relying on headers they no longer reached directly. * Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call * Include Headers That Files Reached Through Ones the Cleanup Removed * Drop Includes Duplicated by the Cleanup or by Main's Own Additions * Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
428 lines
14 KiB
C++
428 lines
14 KiB
C++
#include "IconManager.hpp"
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <cstring>
|
|
#include <imgui.h>
|
|
#include <cstdint>
|
|
#include <numeric>
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/nowide/cstdio.hpp>
|
|
#include "nanosvg/nanosvg.h"
|
|
#include "nanosvg/nanosvgrast.h"
|
|
#include "libslic3r/Utils.hpp" // ScopeGuard
|
|
|
|
#include "3DScene.hpp" // glsafe
|
|
#include <glad/gl.h>
|
|
#include <vector>
|
|
#include <utility>
|
|
#include <string>
|
|
|
|
#define STB_RECT_PACK_IMPLEMENTATION
|
|
#include "imgui/imstb_rectpack.h" // distribute rectangles
|
|
|
|
using namespace Slic3r::GUI;
|
|
|
|
namespace priv {
|
|
// set shared pointer to point on bad texture
|
|
static void clear(IconManager::Icons &icons);
|
|
static const std::vector<std::pair<int, bool>>& get_states(IconManager::RasterType type);
|
|
static void draw_transparent_icon(const IconManager::Icon &icon); // only help function
|
|
}
|
|
|
|
IconManager::~IconManager() {
|
|
priv::clear(m_icons);
|
|
// release opengl texture is made in ~GLTexture()
|
|
|
|
if (m_id != 0)
|
|
glsafe(::glDeleteTextures(1, &m_id));
|
|
}
|
|
|
|
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)
|
|
{
|
|
assert(!input.empty());
|
|
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 (size_t 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
|
|
assert(boost::filesystem::exists(i.filepath));
|
|
if (!boost::filesystem::exists(i.filepath))
|
|
continue;
|
|
assert(boost::algorithm::iends_with(i.filepath, ".svg"));
|
|
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)
|
|
{
|
|
assert(!file_paths.empty());
|
|
assert(size.x >= 1);
|
|
assert(size.x < 256*16);
|
|
|
|
// TODO: remove in future
|
|
if (!m_icons.empty()) {
|
|
// not first initialization
|
|
priv::clear(m_icons);
|
|
m_icons.clear();
|
|
m_icons_texture.reset();
|
|
}
|
|
|
|
// only rectangle are supported
|
|
assert(size.x == size.y);
|
|
// no subpixel supported
|
|
unsigned int width = static_cast<unsigned int>(std::abs(std::round(size.x)));
|
|
assert(size.x == static_cast<float>(width));
|
|
|
|
// state order has to match the enum IconState
|
|
const auto& states = priv::get_states(type);
|
|
|
|
bool compress = false;
|
|
bool is_loaded = m_icons_texture.load_from_svg_files_as_sprites_array(file_paths, states, width, compress);
|
|
if (!is_loaded || (size_t) m_icons_texture.get_width() < (states.size() * width) ||
|
|
(size_t) m_icons_texture.get_height() < (file_paths.size() * width)) {
|
|
// bad load of icons, but all usage of m_icons_texture check that texture is initialized
|
|
assert(false);
|
|
m_icons_texture.reset();
|
|
return {};
|
|
}
|
|
|
|
unsigned count_files = file_paths.size();
|
|
// count icons per file
|
|
unsigned count = states.size();
|
|
// create result
|
|
std::vector<Icons> result;
|
|
result.reserve(count_files);
|
|
|
|
Icon def_icon;
|
|
def_icon.tex_id = m_icons_texture.get_id();
|
|
def_icon.size = size;
|
|
|
|
// float beacouse of dividing
|
|
float tex_height = static_cast<float>(m_icons_texture.get_height());
|
|
float tex_width = static_cast<float>(m_icons_texture.get_width());
|
|
|
|
//for (const auto &f: file_paths) {
|
|
for (unsigned f = 0; f < count_files; ++f) {
|
|
// NOTE: there are space between icons
|
|
unsigned start_y = static_cast<unsigned>(f) * (width + 1) + 1;
|
|
float y1 = start_y / tex_height;
|
|
float y2 = (start_y + width) / tex_height;
|
|
Icons file_icons;
|
|
file_icons.reserve(count);
|
|
//for (const auto &s : states) {
|
|
for (unsigned j = 0; j < count; ++j) {
|
|
auto icon = std::make_shared<Icon>(def_icon);
|
|
// NOTE: there are space between icons
|
|
unsigned start_x = static_cast<unsigned>(j) * (width + 1) + 1;
|
|
float x1 = start_x / tex_width;
|
|
float x2 = (start_x + width) / tex_width;
|
|
icon->tl = ImVec2(x1, y1);
|
|
icon->br = ImVec2(x2, y2);
|
|
file_icons.push_back(icon);
|
|
m_icons.push_back(std::move(icon));
|
|
}
|
|
result.emplace_back(std::move(file_icons));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void IconManager::release() {
|
|
BOOST_LOG_TRIVIAL(error) << "Not implemented yet";
|
|
}
|
|
|
|
void priv::clear(IconManager::Icons &icons) {
|
|
std::string message;
|
|
for (auto &icon : icons) {
|
|
// Exist more than this instance of shared ptr?
|
|
long count = icon.use_count();
|
|
if (count != 1) {
|
|
// in existing icon change texture to non existing one
|
|
icon->tex_id = 0;
|
|
|
|
std::string descr =
|
|
((count > 2) ? (std::to_string(count - 1) + "x") : "") + // count
|
|
std::to_string(icon->size.x) + "x" + std::to_string(icon->size.y); // resolution
|
|
if (message.empty())
|
|
message = descr;
|
|
else
|
|
message += ", " + descr;
|
|
}
|
|
}
|
|
|
|
if (!message.empty())
|
|
BOOST_LOG_TRIVIAL(warning) << "There is still used icons(" << message << ").";
|
|
}
|
|
|
|
const std::vector<std::pair<int, bool>> &priv::get_states(IconManager::RasterType type) {
|
|
static std::vector<std::pair<int, bool>> color = {std::make_pair(0, false)};
|
|
static std::vector<std::pair<int, bool>> white = {std::make_pair(1, false)};
|
|
static std::vector<std::pair<int, bool>> gray = {std::make_pair(2, false)};
|
|
static std::vector<std::pair<int, bool>> color_wite_gray = {
|
|
std::make_pair(1, false), // Activable
|
|
std::make_pair(0, false), // Hovered
|
|
std::make_pair(2, false) // Disabled
|
|
};
|
|
|
|
switch (type) {
|
|
case IconManager::RasterType::color: return color;
|
|
case IconManager::RasterType::white_only_data: return white;
|
|
case IconManager::RasterType::gray_only_data: return gray;
|
|
case IconManager::RasterType::color_wite_gray: return color_wite_gray;
|
|
default: return color;
|
|
}
|
|
}
|
|
|
|
void priv::draw_transparent_icon(const IconManager::Icon &icon)
|
|
{
|
|
// Check input
|
|
if (!icon.is_valid()) {
|
|
assert(false);
|
|
BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon.";
|
|
ImGui::Text("?");
|
|
return;
|
|
}
|
|
|
|
// size UV texture coors [in texture ratio]
|
|
ImVec2 size_uv(icon.br.x - icon.tl.x, icon.br.y - icon.tl.y);
|
|
ImVec2 one_px(size_uv.x / icon.size.x, size_uv.y / icon.size.y);
|
|
|
|
// use top left corner of first icon
|
|
IconManager::Icon icon_px = icon; // copy
|
|
// reduce uv coors to one pixel
|
|
icon_px.tl = ImVec2(0, 0);
|
|
icon_px.br = one_px;
|
|
draw(icon_px);
|
|
}
|
|
|
|
#include "imgui/imgui_internal.h" //ImGuiWindow
|
|
#include "slic3r/GUI/GLTexture.hpp"
|
|
namespace Slic3r::GUI {
|
|
|
|
void draw(const IconManager::Icon &icon, const ImVec2 &size, const ImVec4 &tint_col, const ImVec4 &border_col)
|
|
{
|
|
// Check input
|
|
if (!icon.is_valid()) {
|
|
assert(false);
|
|
BOOST_LOG_TRIVIAL(warning) << "Drawing invalid Icon.";
|
|
ImGui::Text("?");
|
|
return;
|
|
}
|
|
ImTextureID id = (void *)static_cast<intptr_t>(icon.tex_id);
|
|
const ImVec2 &s = (size.x < 1 || size.y < 1) ? icon.size : size;
|
|
|
|
// Orca: Align icon center vertically
|
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
|
ImGuiContext &g = *GImGui;
|
|
float cursor_y = window->DC.CursorPos.y;
|
|
float line_height = ImGui::GetTextLineHeight() + g.Style.FramePadding.y * 2;
|
|
int offset_y = (line_height - s.y) / 2; // Make sure its int otherwise it will be pixelated
|
|
window->DC.CursorPos.y += offset_y;
|
|
|
|
ImGui::Image(id, s, icon.tl, icon.br, tint_col, border_col);
|
|
|
|
// Reset offset
|
|
window->DC.CursorPosPrevLine.y = cursor_y;
|
|
}
|
|
|
|
bool clickable(const IconManager::Icon &icon, const IconManager::Icon &icon_hover)
|
|
{
|
|
// check of hover
|
|
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
|
float cursor_x = ImGui::GetCursorPosX()
|
|
- window->DC.GroupOffset.x
|
|
- window->DC.ColumnsOffset.x;
|
|
priv::draw_transparent_icon(icon);
|
|
ImGui::SameLine(cursor_x);
|
|
if (ImGui::IsItemHovered()) {
|
|
// redraw image
|
|
draw(icon_hover);
|
|
} else {
|
|
// redraw normal image
|
|
draw(icon);
|
|
}
|
|
return ImGui::IsItemClicked();
|
|
}
|
|
|
|
bool button(const IconManager::Icon &activ, const IconManager::Icon &hover, const IconManager::Icon &disable, bool disabled)
|
|
{
|
|
if (disabled) {
|
|
draw(disable);
|
|
return false;
|
|
}
|
|
return clickable(activ, hover);
|
|
}
|
|
|
|
} // namespace Slic3r::GUI
|