mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 15:30:00 +01:00
NEW support for pen tablet pressure in Texture Painting. Applies to brush size and/or pressure. TODO: check OSX
This commit is contained in:
@@ -103,6 +103,22 @@ if(UNIX)
|
|||||||
${X11_INCLUDE_DIR}
|
${X11_INCLUDE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# libinput and libudev for tablet pressure support
|
||||||
|
if (PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(LIBINPUT libinput>=1.19)
|
||||||
|
pkg_check_modules(LIBUDEV libudev)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
||||||
|
include_directories(${LIBINPUT_INCLUDE_DIRS} ${LIBUDEV_INCLUDE_DIRS})
|
||||||
|
link_directories(${LIBINPUT_LIBRARY_DIRS} ${LIBUDEV_LIBRARY_DIRS})
|
||||||
|
add_definitions(-DHAVE_LIBINPUT)
|
||||||
|
macro_log_feature(LIBINPUT_FOUND "libinput" "Input device library for tablet support" "https://wayland.freedesktop.org/libinput" FALSE)
|
||||||
|
macro_log_feature(LIBUDEV_FOUND "libudev" "Device management library" "https://www.freedesktop.org/software/systemd/man/libudev.html" FALSE)
|
||||||
|
else()
|
||||||
|
message(STATUS "libinput or libudev not found - tablet pressure support will be disabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
add_definitions(-DUNIX)
|
add_definitions(-DUNIX)
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
|
|||||||
122
osx/TabletInput_macos.mm
Normal file
122
osx/TabletInput_macos.mm
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of vimix - video live mixer
|
||||||
|
*
|
||||||
|
* **Copyright** (C) 2019-2025 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifdef APPLE
|
||||||
|
|
||||||
|
#include "TabletInput.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
TabletInput::TabletInput()
|
||||||
|
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
||||||
|
, active_(false)
|
||||||
|
, monitor_(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletInput::~TabletInput()
|
||||||
|
{
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabletInput::init(void* platform_handle)
|
||||||
|
{
|
||||||
|
// Create event monitor for tablet events
|
||||||
|
NSEventMask mask = NSEventMaskTabletPoint |
|
||||||
|
NSEventMaskTabletProximity |
|
||||||
|
NSEventMaskLeftMouseDown |
|
||||||
|
NSEventMaskLeftMouseUp |
|
||||||
|
NSEventMaskLeftMouseDragged;
|
||||||
|
|
||||||
|
// Capture reference to data_ for the block
|
||||||
|
TabletData *dataPtr = &data_;
|
||||||
|
|
||||||
|
id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:mask
|
||||||
|
handler:^NSEvent *(NSEvent *event) {
|
||||||
|
|
||||||
|
// Check for tablet point events
|
||||||
|
if (event.type == NSEventTypeTabletPoint ||
|
||||||
|
(event.subtype == NSEventSubtypeTabletPoint)) {
|
||||||
|
|
||||||
|
dataPtr->has_pressure = true;
|
||||||
|
dataPtr->pressure = event.pressure;
|
||||||
|
dataPtr->tilt_x = event.tilt.x;
|
||||||
|
dataPtr->tilt_y = event.tilt.y;
|
||||||
|
dataPtr->tip_down = (event.pressure > 0.0f);
|
||||||
|
dataPtr->in_proximity = true;
|
||||||
|
}
|
||||||
|
// Handle proximity events
|
||||||
|
else if (event.type == NSEventTypeTabletProximity) {
|
||||||
|
dataPtr->in_proximity = event.isEnteringProximity;
|
||||||
|
if (!dataPtr->in_proximity) {
|
||||||
|
dataPtr->pressure = 0.0f;
|
||||||
|
dataPtr->tilt_x = 0.0f;
|
||||||
|
dataPtr->tilt_y = 0.0f;
|
||||||
|
dataPtr->tip_down = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to regular mouse events if tablet is in proximity
|
||||||
|
else if (event.type == NSEventTypeLeftMouseDown) {
|
||||||
|
if (dataPtr->in_proximity && dataPtr->pressure == 0.0f) {
|
||||||
|
// Set minimal pressure if tablet is near but not reporting pressure
|
||||||
|
dataPtr->pressure = 0.1f;
|
||||||
|
dataPtr->tip_down = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (event.type == NSEventTypeLeftMouseUp) {
|
||||||
|
if (dataPtr->in_proximity) {
|
||||||
|
dataPtr->tip_down = false;
|
||||||
|
if (!dataPtr->in_proximity) {
|
||||||
|
dataPtr->pressure = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}];
|
||||||
|
|
||||||
|
monitor_ = (__bridge_retained void*)monitor;
|
||||||
|
active_ = true;
|
||||||
|
|
||||||
|
Log::Info("TabletInput: macOS tablet input initialized (NSEvent)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::pollEvents()
|
||||||
|
{
|
||||||
|
// Events are handled automatically by the monitor callback
|
||||||
|
// Nothing to do here for macOS
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::terminate()
|
||||||
|
{
|
||||||
|
if (monitor_) {
|
||||||
|
id monitor = (__bridge_transfer id)monitor_;
|
||||||
|
[NSEvent removeMonitor:monitor];
|
||||||
|
monitor_ = nullptr;
|
||||||
|
}
|
||||||
|
data_.pressure = 0.0f;
|
||||||
|
data_.tilt_x = 0.0f;
|
||||||
|
data_.tilt_y = 0.0f;
|
||||||
|
data_.in_proximity = false;
|
||||||
|
data_.tip_down = false;
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APPLE
|
||||||
Binary file not shown.
@@ -35,7 +35,7 @@ float sdSegment( in vec2 v1, in vec2 v2, float r )
|
|||||||
{
|
{
|
||||||
vec2 ba = v2 - v1;
|
vec2 ba = v2 - v1;
|
||||||
vec2 pa = -v1;
|
vec2 pa = -v1;
|
||||||
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
|
float h = clamp( dot(pa,ba) / dot(ba,ba), 0.0, 1.0 );
|
||||||
return length(pa-h*ba) / r;
|
return length(pa-h*ba) / r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,12 +103,27 @@ set(VMIX_SRCS
|
|||||||
Audio.cpp
|
Audio.cpp
|
||||||
TextSource.cpp
|
TextSource.cpp
|
||||||
ShaderSource.cpp
|
ShaderSource.cpp
|
||||||
|
TabletInput.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
#####
|
#####
|
||||||
##### DEFINE THE TARGET (OS specific)
|
##### DEFINE THE TARGET (OS specific)
|
||||||
#####
|
#####
|
||||||
|
|
||||||
|
IF(APPLE)
|
||||||
|
|
||||||
|
# Add macOS tablet input implementation
|
||||||
|
list(APPEND VMIX_SRCS ${CMAKE_SOURCE_DIR}/osx/TabletInput_macos.mm)
|
||||||
|
# Enable Objective-C++
|
||||||
|
set_source_files_properties(${CMAKE_SOURCE_DIR}/osx/TabletInput_macos.mm PROPERTIES COMPILE_FLAGS "-x objective-c++")
|
||||||
|
|
||||||
|
ELSE(APPLE)
|
||||||
|
|
||||||
|
# Add Linux tablet input implementation
|
||||||
|
list(APPEND VMIX_SRCS TabletInput_linux.cpp)
|
||||||
|
|
||||||
|
ENDIF(APPLE)
|
||||||
|
|
||||||
IF(APPLE)
|
IF(APPLE)
|
||||||
|
|
||||||
# set icon
|
# set icon
|
||||||
@@ -149,6 +164,11 @@ ELSE(APPLE)
|
|||||||
X11::xcb
|
X11::xcb
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add libinput and libudev for tablet support on Linux
|
||||||
|
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
||||||
|
list(APPEND PLATFORM_LIBS ${LIBINPUT_LIBRARIES} ${LIBUDEV_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
ENDIF(APPLE)
|
ENDIF(APPLE)
|
||||||
|
|
||||||
#####
|
#####
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ void MaskShader::reset()
|
|||||||
size = glm::vec2(1.f, 1.f);
|
size = glm::vec2(1.f, 1.f);
|
||||||
|
|
||||||
// default brush
|
// default brush
|
||||||
cursor = glm::vec4(-10.f, -10.f, 1.f, 1.f);
|
cursor = glm::vec4(-10.f, -10.f, -10.f, -10.f);
|
||||||
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
||||||
option = 0;
|
option = 0;
|
||||||
effect = 0;
|
effect = 0;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
#include "Primitives.h"
|
#include "Primitives.h"
|
||||||
|
|
||||||
#include "RenderingManager.h"
|
#include "RenderingManager.h"
|
||||||
|
#include "TabletInput.h"
|
||||||
|
|
||||||
// GDBus for screensaver inhibition (works on both X11 and Wayland)
|
// GDBus for screensaver inhibition (works on both X11 and Wayland)
|
||||||
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
||||||
@@ -567,6 +568,9 @@ bool Rendering::init()
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Initialize tablet input for pressure support
|
||||||
|
TabletInput::instance().init();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,6 +624,11 @@ void Rendering::draw()
|
|||||||
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
|
// Poll tablet input for pressure support
|
||||||
|
#ifndef WIN32
|
||||||
|
TabletInput::instance().pollEvents();
|
||||||
|
#endif
|
||||||
|
|
||||||
// change windows fullscreen mode if requested
|
// change windows fullscreen mode if requested
|
||||||
main_.changeFullscreen_();
|
main_.changeFullscreen_();
|
||||||
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
|
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
|
||||||
@@ -675,6 +684,11 @@ void Rendering::draw()
|
|||||||
|
|
||||||
void Rendering::terminate()
|
void Rendering::terminate()
|
||||||
{
|
{
|
||||||
|
// Terminate tablet input
|
||||||
|
#ifndef WIN32
|
||||||
|
TabletInput::instance().terminate();
|
||||||
|
#endif
|
||||||
|
|
||||||
// terminate all windows
|
// terminate all windows
|
||||||
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
|
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
|
||||||
it->terminate();
|
it->terminate();
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ void Settings::Save(uint64_t runtime, const std::string &filename)
|
|||||||
// Brush
|
// Brush
|
||||||
XMLElement *BrushNode = xmlDoc.NewElement( "Brush" );
|
XMLElement *BrushNode = xmlDoc.NewElement( "Brush" );
|
||||||
BrushNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, application.brush) );
|
BrushNode->InsertEndChild( XMLElementFromGLM(&xmlDoc, application.brush) );
|
||||||
|
BrushNode->SetAttribute("brush_pressure_mode", application.brush_pressure_mode);
|
||||||
pRoot->InsertEndChild(BrushNode);
|
pRoot->InsertEndChild(BrushNode);
|
||||||
|
|
||||||
// Pointer
|
// Pointer
|
||||||
@@ -700,6 +701,7 @@ void Settings::Load(const std::string &filename)
|
|||||||
XMLElement * brushnode = pRoot->FirstChildElement("Brush");
|
XMLElement * brushnode = pRoot->FirstChildElement("Brush");
|
||||||
if (brushnode != nullptr) {
|
if (brushnode != nullptr) {
|
||||||
tinyxml2::XMLElementToGLM( brushnode->FirstChildElement("vec3"), application.brush);
|
tinyxml2::XMLElementToGLM( brushnode->FirstChildElement("vec3"), application.brush);
|
||||||
|
brushnode->QueryIntAttribute("brush_pressure_mode", &application.brush_pressure_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer
|
// Pointer
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ struct Application
|
|||||||
|
|
||||||
// settings brush texture paint
|
// settings brush texture paint
|
||||||
glm::vec3 brush;
|
glm::vec3 brush;
|
||||||
|
int brush_pressure_mode;
|
||||||
|
|
||||||
// settings render
|
// settings render
|
||||||
RenderConfig render;
|
RenderConfig render;
|
||||||
@@ -396,6 +397,7 @@ struct Application
|
|||||||
current_view = 1;
|
current_view = 1;
|
||||||
current_workspace= 3;
|
current_workspace= 3;
|
||||||
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
brush = glm::vec3(0.5f, 0.1f, 0.f);
|
||||||
|
brush_pressure_mode = 0;
|
||||||
num_output_windows = 1;
|
num_output_windows = 1;
|
||||||
windows = std::vector<WindowConfig>(1+MAX_OUTPUT_WINDOW);
|
windows = std::vector<WindowConfig>(1+MAX_OUTPUT_WINDOW);
|
||||||
windows[0].w = 1600;
|
windows[0].w = 1600;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|||||||
30
src/TabletInput.cpp
Normal file
30
src/TabletInput.cpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of vimix - video live mixer
|
||||||
|
*
|
||||||
|
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "TabletInput.h"
|
||||||
|
|
||||||
|
TabletInput& TabletInput::instance()
|
||||||
|
{
|
||||||
|
static TabletInput instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform-specific implementations are in separate files:
|
||||||
|
// - TabletInput_linux.cpp (Linux with libinput)
|
||||||
|
// - TabletInput_macos.mm (macOS with NSEvent)
|
||||||
92
src/TabletInput.h
Normal file
92
src/TabletInput.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of vimix - video live mixer
|
||||||
|
*
|
||||||
|
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifndef TABLETINPUT_H
|
||||||
|
#define TABLETINPUT_H
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
// Platform-specific forward declarations
|
||||||
|
#if defined(LINUX) && defined(HAVE_LIBINPUT)
|
||||||
|
struct libinput;
|
||||||
|
struct udev;
|
||||||
|
#elif defined(APPLE)
|
||||||
|
#ifdef __OBJC__
|
||||||
|
@class NSEvent;
|
||||||
|
#else
|
||||||
|
class NSEvent;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cross-platform tablet/stylus input manager
|
||||||
|
*
|
||||||
|
* Provides normalized pressure values (0.0-1.0) from pen/stylus devices
|
||||||
|
* across Linux (libinput) and macOS (NSEvent)
|
||||||
|
*/
|
||||||
|
class TabletInput {
|
||||||
|
public:
|
||||||
|
struct TabletData {
|
||||||
|
float pressure; // 0.0 - 1.0
|
||||||
|
bool has_pressure; // Stylius has pressure
|
||||||
|
float tilt_x; // -1.0 to 1.0 (optional)
|
||||||
|
float tilt_y; // -1.0 to 1.0 (optional)
|
||||||
|
bool in_proximity; // Is stylus near/touching surface
|
||||||
|
bool tip_down; // Is stylus tip pressed
|
||||||
|
};
|
||||||
|
|
||||||
|
static TabletInput& instance();
|
||||||
|
|
||||||
|
// Initialize tablet input system
|
||||||
|
bool init(void* platform_handle = nullptr);
|
||||||
|
|
||||||
|
// Poll for new tablet events (call once per frame)
|
||||||
|
void pollEvents();
|
||||||
|
|
||||||
|
// Clean up resources
|
||||||
|
void terminate();
|
||||||
|
|
||||||
|
// Get current tablet data
|
||||||
|
const TabletData& getData() const { return data_; }
|
||||||
|
|
||||||
|
// Quick accessors
|
||||||
|
float getPressure() const { return data_.pressure; }
|
||||||
|
bool isPressed() const { return data_.tip_down || data_.in_proximity; }
|
||||||
|
|
||||||
|
// status
|
||||||
|
bool isEnabled() const { return active_; }
|
||||||
|
bool hasPressure() const { return isEnabled() && data_.has_pressure; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TabletInput();
|
||||||
|
~TabletInput();
|
||||||
|
|
||||||
|
TabletData data_;
|
||||||
|
bool active_;
|
||||||
|
|
||||||
|
#if defined(LINUX) && defined(HAVE_LIBINPUT)
|
||||||
|
struct udev *udev_;
|
||||||
|
struct libinput *li_;
|
||||||
|
int fd_;
|
||||||
|
#elif defined(APPLE)
|
||||||
|
void* monitor_; // Event monitor handle
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TABLETINPUT_H
|
||||||
205
src/TabletInput_linux.cpp
Normal file
205
src/TabletInput_linux.cpp
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of vimix - video live mixer
|
||||||
|
*
|
||||||
|
* **Copyright** (C) 2019-2025 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifdef LINUX
|
||||||
|
|
||||||
|
#include "TabletInput.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBINPUT
|
||||||
|
#include <libinput.h>
|
||||||
|
#include <libudev.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
static int open_restricted(const char *path, int flags, void *user_data)
|
||||||
|
{
|
||||||
|
int fd = open(path, flags);
|
||||||
|
return fd < 0 ? -errno : fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_restricted(int fd, void *user_data)
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct libinput_interface interface = {
|
||||||
|
.open_restricted = open_restricted,
|
||||||
|
.close_restricted = close_restricted,
|
||||||
|
};
|
||||||
|
|
||||||
|
TabletInput::TabletInput()
|
||||||
|
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
||||||
|
, active_(false)
|
||||||
|
, udev_(nullptr)
|
||||||
|
, li_(nullptr)
|
||||||
|
, fd_(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletInput::~TabletInput()
|
||||||
|
{
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabletInput::init(void* platform_handle)
|
||||||
|
{
|
||||||
|
udev_ = udev_new();
|
||||||
|
if (!udev_) {
|
||||||
|
Log::Info("TabletInput: Failed to initialize udev");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
li_ = libinput_udev_create_context(&interface, nullptr, udev_);
|
||||||
|
if (!li_) {
|
||||||
|
Log::Info("TabletInput: Failed to create libinput context");
|
||||||
|
udev_unref(udev_);
|
||||||
|
udev_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libinput_udev_assign_seat(li_, "seat0") != 0) {
|
||||||
|
Log::Info("TabletInput: Failed to assign seat");
|
||||||
|
libinput_unref(li_);
|
||||||
|
li_ = nullptr;
|
||||||
|
udev_unref(udev_);
|
||||||
|
udev_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_ = libinput_get_fd(li_);
|
||||||
|
|
||||||
|
// Set non-blocking
|
||||||
|
int flags = fcntl(fd_, F_GETFL, 0);
|
||||||
|
fcntl(fd_, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
active_ = true;
|
||||||
|
Log::Info("TabletInput: Linux tablet input initialized (libinput)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::pollEvents()
|
||||||
|
{
|
||||||
|
if (!li_) return;
|
||||||
|
|
||||||
|
libinput_dispatch(li_);
|
||||||
|
|
||||||
|
struct libinput_event *event;
|
||||||
|
while ((event = libinput_get_event(li_)) != nullptr) {
|
||||||
|
enum libinput_event_type type = libinput_event_get_type(event);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
|
||||||
|
case LIBINPUT_EVENT_TABLET_TOOL_TIP:
|
||||||
|
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: {
|
||||||
|
struct libinput_event_tablet_tool *tablet_event =
|
||||||
|
libinput_event_get_tablet_tool_event(event);
|
||||||
|
|
||||||
|
// Update pressure
|
||||||
|
if (libinput_event_tablet_tool_pressure_has_changed(tablet_event)) {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
data_.pressure = libinput_event_tablet_tool_get_pressure(tablet_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tilt (if available)
|
||||||
|
if (libinput_event_tablet_tool_tilt_x_has_changed(tablet_event)) {
|
||||||
|
data_.tilt_x = libinput_event_tablet_tool_get_tilt_x(tablet_event) / 90.0f;
|
||||||
|
}
|
||||||
|
if (libinput_event_tablet_tool_tilt_y_has_changed(tablet_event)) {
|
||||||
|
data_.tilt_y = libinput_event_tablet_tool_get_tilt_y(tablet_event) / 90.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update proximity
|
||||||
|
if (type == LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) {
|
||||||
|
data_.in_proximity = (libinput_event_tablet_tool_get_proximity_state(tablet_event)
|
||||||
|
== LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
|
||||||
|
if (!data_.in_proximity) {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
data_.pressure = 0.0f;
|
||||||
|
data_.tip_down = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tip state
|
||||||
|
if (type == LIBINPUT_EVENT_TABLET_TOOL_TIP) {
|
||||||
|
data_.tip_down = (libinput_event_tablet_tool_get_tip_state(tablet_event)
|
||||||
|
== LIBINPUT_TABLET_TOOL_TIP_DOWN);
|
||||||
|
if (!data_.tip_down) {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
data_.pressure = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
libinput_event_destroy(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::terminate()
|
||||||
|
{
|
||||||
|
if (li_) {
|
||||||
|
libinput_unref(li_);
|
||||||
|
li_ = nullptr;
|
||||||
|
}
|
||||||
|
if (udev_) {
|
||||||
|
udev_unref(udev_);
|
||||||
|
udev_ = nullptr;
|
||||||
|
}
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // !HAVE_LIBINPUT
|
||||||
|
|
||||||
|
// Stub implementation when libinput is not available
|
||||||
|
|
||||||
|
TabletInput::TabletInput()
|
||||||
|
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
||||||
|
, active_(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletInput::~TabletInput()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabletInput::init(void* platform_handle)
|
||||||
|
{
|
||||||
|
Log::Info("TabletInput: libinput not available - tablet pressure support disabled");
|
||||||
|
active_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::pollEvents()
|
||||||
|
{
|
||||||
|
// No-op when libinput is not available
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::terminate()
|
||||||
|
{
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_LIBINPUT
|
||||||
|
|
||||||
|
#endif // LINUX
|
||||||
@@ -17,7 +17,10 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
#include "IconsFontAwesome5.h"
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
#include <glm/common.hpp>
|
||||||
|
#include <glm/ext/vector_float2.hpp>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/constants.hpp>
|
#include <glm/gtc/constants.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
@@ -43,6 +46,14 @@
|
|||||||
#include "ActionManager.h"
|
#include "ActionManager.h"
|
||||||
#include "DialogToolkit.h"
|
#include "DialogToolkit.h"
|
||||||
#include "MousePointer.h"
|
#include "MousePointer.h"
|
||||||
|
#include "TabletInput.h"
|
||||||
|
|
||||||
|
enum TabletInputFlags_
|
||||||
|
{
|
||||||
|
TabletInput_none = 0,
|
||||||
|
TabletInput_brush_size = 1 << 1,
|
||||||
|
TabletInput_brush_pressure = 1 << 2
|
||||||
|
};
|
||||||
|
|
||||||
#include "TextureView.h"
|
#include "TextureView.h"
|
||||||
|
|
||||||
@@ -227,7 +238,8 @@ TextureView::TextureView() : View(TEXTURE), edit_source_(nullptr), need_edit_upd
|
|||||||
|
|
||||||
stored_mask_size_ = glm::vec3(0.f);
|
stored_mask_size_ = glm::vec3(0.f);
|
||||||
show_cursor_forced_ = false;
|
show_cursor_forced_ = false;
|
||||||
scene_brush_pos = glm::vec3(0.f);
|
scene_brush_pos = glm::vec3(100.f);
|
||||||
|
previous_scene_brush_pos = glm::vec3(0.f);
|
||||||
|
|
||||||
// replace grid with appropriate one
|
// replace grid with appropriate one
|
||||||
translation_grid_ = new TranslationGrid(scene.root());
|
translation_grid_ = new TranslationGrid(scene.root());
|
||||||
@@ -795,7 +807,7 @@ void TextureView::draw()
|
|||||||
if (mask_cursor_paint_ > 0) {
|
if (mask_cursor_paint_ > 0) {
|
||||||
|
|
||||||
ImGui::SameLine(0, 50);
|
ImGui::SameLine(0, 50);
|
||||||
if (ImGui::Button(ICON_FA_PEN ICON_FA_SORT_DOWN ))
|
if (ImGui::Button(ICON_FA_PEN_NIB ICON_FA_SORT_DOWN ))
|
||||||
ImGui::OpenPopup("brush_shape_popup");
|
ImGui::OpenPopup("brush_shape_popup");
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
ImGuiToolkit::ToolTip("Shape");
|
ImGuiToolkit::ToolTip("Shape");
|
||||||
@@ -823,6 +835,17 @@ void TextureView::draw()
|
|||||||
int pixel_size = int(Settings::application.brush.x * edit_source_->frame()->height() );
|
int pixel_size = int(Settings::application.brush.x * edit_source_->frame()->height() );
|
||||||
show_cursor_forced_ = true;
|
show_cursor_forced_ = true;
|
||||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
|
||||||
|
// toggle to enable tablet input on brush size
|
||||||
|
if(TabletInput::instance().isEnabled() && TabletInput::instance().hasPressure()) {
|
||||||
|
static bool enable_tablet_input = false;
|
||||||
|
enable_tablet_input = Settings::application.brush_pressure_mode & TabletInput_brush_size;
|
||||||
|
ImGuiToolkit::ButtonIconToggle(13, 0, &enable_tablet_input, "Tablet pressure sensitive");
|
||||||
|
if (enable_tablet_input)
|
||||||
|
Settings::application.brush_pressure_mode |= TabletInput_brush_size;
|
||||||
|
else
|
||||||
|
Settings::application.brush_pressure_mode &= ~TabletInput_brush_size;
|
||||||
|
}
|
||||||
|
// max brush size
|
||||||
ImGuiToolkit::Indication("Large ", 16, 1);
|
ImGuiToolkit::Indication("Large ", 16, 1);
|
||||||
if (ImGui::VSliderInt("##BrushSize", ImVec2(30,260), &pixel_size, pixel_size_min, pixel_size_max, "") ){
|
if (ImGui::VSliderInt("##BrushSize", ImVec2(30,260), &pixel_size, pixel_size_min, pixel_size_max, "") ){
|
||||||
Settings::application.brush.x = CLAMP(float(pixel_size) / edit_source_->frame()->height(), BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
|
Settings::application.brush.x = CLAMP(float(pixel_size) / edit_source_->frame()->height(), BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
|
||||||
@@ -849,6 +872,17 @@ void TextureView::draw()
|
|||||||
if (ImGui::BeginPopup("brush_pressure_popup", ImGuiWindowFlags_NoMove))
|
if (ImGui::BeginPopup("brush_pressure_popup", ImGuiWindowFlags_NoMove))
|
||||||
{
|
{
|
||||||
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT);
|
||||||
|
// toggle to enable tablet input on brush pressure
|
||||||
|
if(TabletInput::instance().isEnabled() && TabletInput::instance().hasPressure()) {
|
||||||
|
static bool enable_tablet_input = false;
|
||||||
|
enable_tablet_input = Settings::application.brush_pressure_mode & TabletInput_brush_pressure;
|
||||||
|
ImGuiToolkit::ButtonIconToggle(13, 0, &enable_tablet_input, "Tablet pressure sensitive");
|
||||||
|
if (enable_tablet_input)
|
||||||
|
Settings::application.brush_pressure_mode |= TabletInput_brush_pressure;
|
||||||
|
else
|
||||||
|
Settings::application.brush_pressure_mode &= ~TabletInput_brush_pressure;
|
||||||
|
}
|
||||||
|
// max brush pressure
|
||||||
ImGuiToolkit::Indication("Light ", ICON_FA_FEATHER_ALT);
|
ImGuiToolkit::Indication("Light ", ICON_FA_FEATHER_ALT);
|
||||||
ImGui::VSliderFloat("##BrushPressure", ImVec2(30,260), &Settings::application.brush.y, BRUSH_MAX_PRESS, BRUSH_MIN_PRESS, "", 0.3f);
|
ImGui::VSliderFloat("##BrushPressure", ImVec2(30,260), &Settings::application.brush.y, BRUSH_MAX_PRESS, BRUSH_MIN_PRESS, "", 0.3f);
|
||||||
if (ImGui::IsItemHovered() || ImGui::IsItemActive() ) {
|
if (ImGui::IsItemHovered() || ImGui::IsItemActive() ) {
|
||||||
@@ -892,7 +926,7 @@ void TextureView::draw()
|
|||||||
}
|
}
|
||||||
if (e>0) {
|
if (e>0) {
|
||||||
edit_source_->maskShader()->effect = e;
|
edit_source_->maskShader()->effect = e;
|
||||||
edit_source_->maskShader()->cursor = glm::vec4(100.0, 100.0, 0.f, 0.f);
|
edit_source_->maskShader()->cursor = glm::vec4(100.0, 100.0,100.f, 100.f);
|
||||||
edit_source_->touch(Source::SourceUpdate_Mask);
|
edit_source_->touch(Source::SourceUpdate_Mask);
|
||||||
Action::manager().store(oss.str());
|
Action::manager().store(oss.str());
|
||||||
}
|
}
|
||||||
@@ -1119,7 +1153,19 @@ View::Cursor TextureView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pa
|
|||||||
scene_brush_pos = grid->snap(scene_brush_pos);
|
scene_brush_pos = grid->snap(scene_brush_pos);
|
||||||
// inform shader of a cursor action : coordinates and crop scaling
|
// inform shader of a cursor action : coordinates and crop scaling
|
||||||
edit_source_->maskShader()->size = edit_source_->mixingsurface_->scale_;
|
edit_source_->maskShader()->size = edit_source_->mixingsurface_->scale_;
|
||||||
|
|
||||||
|
// Apply tablet pressure to brush if available
|
||||||
|
if (TabletInput::instance().hasPressure() && TabletInput::instance().isPressed()) {
|
||||||
|
float tablet_pressure = TabletInput::instance().getPressure();
|
||||||
|
// apply pressure depending on mode
|
||||||
|
// scale size of brush
|
||||||
|
if (Settings::application.brush_pressure_mode & TabletInput_brush_size)
|
||||||
|
edit_source_->maskShader()->brush.x = CLAMP( Settings::application.brush.x * tablet_pressure, BRUSH_MIN_SIZE, BRUSH_MAX_SIZE);
|
||||||
|
// transparency pressure
|
||||||
|
if (Settings::application.brush_pressure_mode & TabletInput_brush_pressure)
|
||||||
|
edit_source_->maskShader()->brush.y = CLAMP( Settings::application.brush.x * tablet_pressure, BRUSH_MIN_PRESS, BRUSH_MAX_PRESS);
|
||||||
|
}
|
||||||
|
|
||||||
// inform shader of a cursor action : coordinates and crop scaling
|
// inform shader of a cursor action : coordinates and crop scaling
|
||||||
edit_source_->maskShader()->cursor = glm::vec4(scene_brush_pos.x - shift_crop_.x,
|
edit_source_->maskShader()->cursor = glm::vec4(scene_brush_pos.x - shift_crop_.x,
|
||||||
scene_brush_pos.y - shift_crop_.y,
|
scene_brush_pos.y - shift_crop_.y,
|
||||||
@@ -1560,7 +1606,8 @@ void TextureView::terminate(bool force)
|
|||||||
// special case for texture paint: store image on mouse release (end of action PAINT)
|
// special case for texture paint: store image on mouse release (end of action PAINT)
|
||||||
if ( edit_source_ != nullptr && current_action_.find(MaskShader::mask_names[MaskShader::PAINT]) != std::string::npos ) {
|
if ( edit_source_ != nullptr && current_action_.find(MaskShader::mask_names[MaskShader::PAINT]) != std::string::npos ) {
|
||||||
edit_source_->storeMask();
|
edit_source_->storeMask();
|
||||||
edit_source_->maskShader()->cursor = glm::vec4(100.0, 100.0, 0.f, 0.f);
|
edit_source_->maskShader()->cursor = glm::vec4(100.0, 100.0, 100.f, 100.f);
|
||||||
|
edit_source_->maskShader()->size = glm::vec2(stored_mask_size_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// View default termination of action
|
// View default termination of action
|
||||||
|
|||||||
Reference in New Issue
Block a user