* Add runtime display backend detection for Wayland support Add LinuxDisplayBackend utility to detect X11 vs Wayland at runtime using GDK_IS_X11_DISPLAY / GDK_IS_WAYLAND_DISPLAY macros. This is the foundation for removing the forced GDK_BACKEND=x11 and enabling native Wayland support. - New files: LinuxDisplayBackend.hpp/.cpp with get_linux_display_backend(), is_running_on_wayland(), and is_running_on_x11() - Propagate wxHAVE_GDK_X11 / wxHAVE_GDK_WAYLAND from FindGTK3.cmake as compile definitions to libslic3r_gui - No-op on non-Linux platforms (returns Unknown / false) * Fix Phase 1 code quality: pragma once, source ordering, static cache * Make X11 initialization conditional for Wayland support Remove the unconditional GDK_BACKEND=x11 force that blocked native Wayland. Replace with conditional logic: - EGL safety fallback: re-force X11 only when wxUSE_GLCANVAS_EGL is off and WAYLAND_DISPLAY is set, with a warning log - XInitThreads() only called when DISPLAY is set (X11 in use) - __GLX_VENDOR_LIBRARY_NAME only set when DISPLAY is present (GLX-specific) - WEBKIT_DISABLE_COMPOSITING_MODE only set under XWayland (both DISPLAY and WAYLAND_DISPLAY present) - Guard X11/Xlib.h include with __has_include for robustness - Restore display validation to accept either DISPLAY or WAYLAND_DISPLAY This is Phase 2 of the Wayland support plan. * Fix Phase 2: safer EGL macro check, add clarifying comments * Add GLAD2 library and replace GLEW linkage in build system Set up GLAD2 as a static library to replace GLEW for OpenGL loading. GLAD2 supports both GLX and EGL, which is required for Wayland support. - Create src/glad/ with pre-generated GLAD2 sources (GL 4.6 compat) - Add src/glad/CMakeLists.txt building glad as a static library - Wire glad into src/CMakeLists.txt before libvgcode - Modify libvgcode to use shared glad for GL path (keeps local copy only for GLES2/Emscripten) to avoid duplicate symbol conflicts - Replace GLEW::GLEW with glad in libslic3r_gui link libraries Note: GLEW is kept in deps for OpenCSG. Code migration from GL/glew.h to glad/gl.h headers will follow in Phase 3B+3C. * Fix Phase 3A+3D: libvgcode GLAD include, dead files, dlopen dep, OpenGL link var * Migrate from GLEW to GLAD: replace headers and API calls across codebase Replace all #include <GL/glew.h> with <glad/gl.h> across 49 source files. Migrate GLEW API calls to GLAD equivalents: - glewInit/glewExperimental -> gladLoaderLoadGL() - GLEW_EXT_* / GLEW_ARB_* extension checks -> GLAD_GL_EXT_* / GLAD_GL_ARB_* - Remove GLEW-specific EGL/GLX mismatch #error guards (not needed with GLAD) - Replace unavailable EXT symbols with core GL equivalents in GLCanvas3D.cpp (GL_MAX_SAMPLES, glRenderbufferStorageMultisample, glBlitFramebuffer, GL_READ/DRAW_FRAMEBUFFER) - Update log messages from glewInit to gladLoadGL * Fix Phase 3B+3C: remove GLEW find, clean EXT symbols, update attribution - Remove find_package(GLEW) block from root CMakeLists.txt since GLEW is no longer linked by any main application code - Remove "glew" from SLIC3R_STATIC option description - Replace all remaining EXT framebuffer symbols with core equivalents in render_thumbnail_framebuffer_ext and _rectangular_selection_picking_pass - Update AboutDialog credits from GLEW to GLAD * Enable EGL in wxWidgets and add runtime GLX/EGL selection for Wayland - Set wxUSE_GLCANVAS_EGL=ON in wxWidgets build and Flatpak manifest - Add PreferGLX() call on X11 sessions for driver compatibility - Remove Phase 2 safety fallback (EGL is now always compiled in) - Guard SwapBuffers against hidden canvases to prevent Wayland stalls * Fix Phase 4: move PreferGLX to app startup, fix FPS counter guard Move wxGLCanvas::PreferGLX() from OpenGLManager::create_wxglcanvas() (static initializer) to GUI_App::on_init_inner() before any wxGLCanvas is constructed. This prevents a race where SkipPartCanvas could trigger wxGLBackend::Init() before the GLX preference is set. The new location also adds explicit is_running_on_wayland() detection with a warning for unknown backends. Move increment_fps_counter() inside the IsShownOnScreen() guard so FPS is only counted when a frame is actually swapped. * Update GLFW from 3.3.7 to 3.4 for runtime Wayland/X11 backend selection Replace the compile-time GLFW_USE_WAYLAND flag (which locked to a single backend) with GLFW 3.4's GLFW_BUILD_WAYLAND + GLFW_BUILD_X11 flags that build both backends and auto-select at runtime based on the available display server. This enables the CLI thumbnail renderer to work on both Wayland and X11 sessions without separate builds. * wayland: Fix UI call sites that rely on global screen coordinates On Wayland, wxGetMousePosition() returns (0,0) and SetPosition() is a no-op for top-level windows. Fix the highest-impact call sites: - GLCanvas3D: Use cached m_mouse.position from event handlers instead of wxGetMousePosition() + ScreenToClient() in get_local_mouse_position() - Plater: Use event-relative coords via ClientToScreen(e.GetPosition()) instead of wxGetMousePosition() in 3 leave-window handlers - BBLTopbar: Use event.GetPosition() and FindToolByPosition() directly in mouse handlers instead of wxGetMousePosition()/FindToolByCurrentPosition() - Search: Use focus-based dismiss logic on Wayland instead of wxGetMousePosition()-based rect checks in SearchDialog and SearchObjectDialog - GUI_App: Skip SetPosition() in window_pos_restore() on Wayland where it is a no-op; still restore size and maximize state - Button: Position tooltip relative to button widget via ClientToScreen instead of wxGetMousePosition() * Fix SearchDialog Wayland dismiss: guard against search_line focus * flatpak: Add Wayland socket permission for native Wayland support * spec * Fix crash on Wayland when wxWidgets lacks EGL support Restore the safety fallback that forces GDK_BACKEND=x11 when wxWidgets was not built with wxUSE_GLCANVAS_EGL=ON. Without this, the GLX backend tries to access a non-existent X11 display on native Wayland, crashing in wxGLCanvas::IsDisplaySupported() with SIGSEGV at offset 0xe4. Also add a defense-in-depth guard in detect_multisample() that skips the IsDisplaySupported call entirely on Wayland without EGL. Root cause: deps/wxWidgets must be rebuilt after enabling EGL. The compile-time check in OrcaSlicer.cpp detects the mismatch and falls back safely. * Fix EGL detection: use wxHAS_EGL instead of wxUSE_GLCANVAS_EGL wxUSE_GLCANVAS_EGL is a CMake build option, NOT a C++ preprocessor macro. The actual macro defined in wxWidgets setup.h is wxHAS_EGL. All compile-time EGL checks were using the wrong macro, causing the safety fallback to always trigger even with a properly built EGL-enabled wxWidgets. * Fix GL function pointers invalidated on Wayland/EGL gladLoaderLoadGL() dlopen's libGL.so.1 to resolve GL function pointers via dlsym, then immediately dlclose's the handle. On X11/GLX this is fine because the GLX context keeps libGL.so mapped. On Wayland/EGL, nothing else holds libGL.so open, so dlclose unmaps it and all function pointers become dangling — causing SIGSEGV on the first GL call. Fix: on Wayland, use gladLoadGL(eglGetProcAddress) which resolves function pointers through the EGL loader without opening/closing libGL.so. * fix crash on start and various rendering issues * fix crash on close * small refactor * move GPU selection to desktop file * clean up a bit * clean up more * fix appimage error
414 lines
14 KiB
C++
414 lines
14 KiB
C++
#include "IconManager.hpp"
|
|
#include <cmath>
|
|
#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>
|
|
|
|
#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
|
|
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
|