Add macOS pen/stylus/tablet driver.
This commit is contained in:
parent
1fc0676270
commit
c49a3a7380
@ -84,6 +84,11 @@ public:
|
||||
Fl_Widget_Tracker& operator=(Fl_Widget_Tracker&&) = delete;
|
||||
~Fl_Widget_Tracker();
|
||||
|
||||
/**
|
||||
Clear the widget pointer.
|
||||
*/
|
||||
void clear() { wp_ = nullptr; }
|
||||
|
||||
/**
|
||||
Returns a pointer to the watched widget.
|
||||
\return nullptr if the widget was deleted.
|
||||
@ -94,13 +99,13 @@ public:
|
||||
Check if the widget was deleted since the tracker was created.
|
||||
\return 1 if the watched widget has been deleted, otherwise 0
|
||||
*/
|
||||
int deleted() {return wp_ == 0;}
|
||||
int deleted() {return wp_ == nullptr;}
|
||||
|
||||
/**
|
||||
Check if the widget exists and was not deleted since the tracker was created.
|
||||
\return 1 if the watched widget exists, otherwise 0
|
||||
*/
|
||||
int exists() { return wp_ != 0; }
|
||||
int exists() { return wp_ != nullptr; }
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -84,12 +84,45 @@ enum class Trait : uint32_t {
|
||||
PROXIMITY = 0x0400,
|
||||
};
|
||||
|
||||
/**
|
||||
\brief Bitwise OR operator for Trait enum.
|
||||
\param lhs Left-hand side trait flags
|
||||
\param rhs Right-hand side trait flags
|
||||
\return Combined trait flags
|
||||
*/
|
||||
inline constexpr Trait operator|(Trait lhs, Trait rhs) {
|
||||
return static_cast<Trait>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Bitwise AND operator for Trait enum.
|
||||
\param lhs Left-hand side trait flags
|
||||
\param rhs Right-hand side trait flags
|
||||
\return Intersection of trait flags
|
||||
*/
|
||||
inline constexpr Trait operator&(Trait lhs, Trait rhs) {
|
||||
return static_cast<Trait>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Bitwise OR assignment operator for Trait enum.
|
||||
\param lhs Left-hand side trait flags (modified in place)
|
||||
\param rhs Right-hand side trait flags
|
||||
\return Reference to modified lhs
|
||||
*/
|
||||
inline Trait& operator|=(Trait& lhs, Trait rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
\brief Bitfield of pen state flags.
|
||||
\see event_state(), event_trigger()
|
||||
*/
|
||||
enum class State : uint32_t {
|
||||
/// No button pressed
|
||||
NONE = 0x0000,
|
||||
/// The tip hovers over the surface but does not touch it
|
||||
TIP_HOVERS = 0x0001,
|
||||
/// The tip touches the surface
|
||||
@ -106,8 +139,42 @@ enum class State : uint32_t {
|
||||
BUTTON2 = 0x0400,
|
||||
/// Barrel button 3 is pressed
|
||||
BUTTON3 = 0x0800,
|
||||
/// Mask for all buttons, tip, and eraser down
|
||||
ANY_DOWN = BUTTON0 | BUTTON1 | BUTTON2 | BUTTON3 | TIP_DOWN | ERASER_DOWN,
|
||||
};
|
||||
|
||||
/*
|
||||
\brief Bitwise OR operator for State enum.
|
||||
\param lhs Left-hand side state flags
|
||||
\param rhs Right-hand side state flags
|
||||
\return Combined state flags
|
||||
*/
|
||||
inline constexpr State operator|(State lhs, State rhs) {
|
||||
return static_cast<State>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/*
|
||||
\brief Bitwise AND operator for State enum.
|
||||
\param lhs Left-hand side state flags
|
||||
\param rhs Right-hand side state flags
|
||||
\return Intersection of state flags
|
||||
*/
|
||||
inline constexpr State operator&(State lhs, State rhs) {
|
||||
return static_cast<State>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/*
|
||||
\brief Bitwise OR assignment operator for State enum.
|
||||
\param lhs Left-hand side state flags (modified in place)
|
||||
\param rhs Right-hand side state flags
|
||||
\return Reference to modified lhs
|
||||
*/
|
||||
inline State& operator|=(State& lhs, State rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
\brief List of pen events.
|
||||
These events extend the standard Fl_Event enumeration.
|
||||
@ -315,6 +382,14 @@ FL_EXPORT extern double event_proximity();
|
||||
*/
|
||||
FL_EXPORT extern State event_state();
|
||||
|
||||
/**
|
||||
* \brief Return true if the corresponding bit is set in the event state.
|
||||
* \return true if any bit is set
|
||||
*/
|
||||
inline bool event_state(State bits) {
|
||||
return ((event_state() & bits) != State::NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Returns the state change that triggered the event.
|
||||
* \return a state with one bit set for the action that triggered this event
|
||||
@ -323,67 +398,6 @@ FL_EXPORT extern State event_trigger();
|
||||
|
||||
/** @} */ // group fl_pen_events
|
||||
|
||||
/**
|
||||
\brief Bitwise OR operator for Trait enum.
|
||||
\param lhs Left-hand side trait flags
|
||||
\param rhs Right-hand side trait flags
|
||||
\return Combined trait flags
|
||||
*/
|
||||
inline constexpr Trait operator|(Trait lhs, Trait rhs) {
|
||||
return static_cast<Trait>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Bitwise AND operator for Trait enum.
|
||||
\param lhs Left-hand side trait flags
|
||||
\param rhs Right-hand side trait flags
|
||||
\return Intersection of trait flags
|
||||
*/
|
||||
inline constexpr Trait operator&(Trait lhs, Trait rhs) {
|
||||
return static_cast<Trait>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Bitwise OR assignment operator for Trait enum.
|
||||
\param lhs Left-hand side trait flags (modified in place)
|
||||
\param rhs Right-hand side trait flags
|
||||
\return Reference to modified lhs
|
||||
*/
|
||||
inline Trait& operator|=(Trait& lhs, Trait rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
/*
|
||||
\brief Bitwise OR operator for State enum.
|
||||
\param lhs Left-hand side state flags
|
||||
\param rhs Right-hand side state flags
|
||||
\return Combined state flags
|
||||
*/
|
||||
inline constexpr State operator|(State lhs, State rhs) {
|
||||
return static_cast<State>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/*
|
||||
\brief Bitwise AND operator for State enum.
|
||||
\param lhs Left-hand side state flags
|
||||
\param rhs Right-hand side state flags
|
||||
\return Intersection of state flags
|
||||
*/
|
||||
inline constexpr State operator&(State lhs, State rhs) {
|
||||
return static_cast<State>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
/*
|
||||
\brief Bitwise OR assignment operator for State enum.
|
||||
\param lhs Left-hand side state flags (modified in place)
|
||||
\param rhs Right-hand side state flags
|
||||
\return Reference to modified lhs
|
||||
*/
|
||||
inline State& operator|=(State& lhs, State rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
} // namespace Pen
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Definition of default Pen/Tablet event driver.
|
||||
// Definition of macOS Cocoa Pen/Tablet event driver.
|
||||
//
|
||||
// Copyright 2025 by Bill Spitzak and others.
|
||||
//
|
||||
@ -19,16 +19,103 @@
|
||||
#include <FL/platform.H>
|
||||
#include <FL/core/pen_events.H>
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Window.H>
|
||||
#include <FL/Fl_Tooltip.H>
|
||||
#include <FL/Fl_Widget_Tracker.H>
|
||||
#include "../../Fl_Screen_Driver.H"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
class Fl_Widget;
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
extern Fl_Window *fl_xmousewin;
|
||||
|
||||
/*
|
||||
Widgets and windows must subscribe to pen events. This is to reduce the amount
|
||||
of events sent into the widget hierarchy.
|
||||
|
||||
Usually there is a pretty small number of subscribers, so looping through the
|
||||
subscriber list should not be an issue.
|
||||
|
||||
All subscribers track their widget. If a widget is deleted while subscribed,
|
||||
including during event handling, the driver will remove the subscription.
|
||||
There is no need to explicitly unsubscribe.
|
||||
*/
|
||||
class Subscriber : public Fl_Widget_Tracker {
|
||||
public:
|
||||
Subscriber(Fl_Widget *w) : Fl_Widget_Tracker(w) { }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Manage a list of subscribers.
|
||||
*/
|
||||
class SubscriberList : public std::map<Fl_Widget*, std::shared_ptr<Subscriber>> {
|
||||
public:
|
||||
SubscriberList() = default;
|
||||
/* Remove subscribers that have a nullptr as a widget */
|
||||
void cleanup() {
|
||||
for (auto it = begin(); it != end(); ) {
|
||||
if (!it->second->widget()) {
|
||||
it = erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Add a new subscriber, or return an existing one. */
|
||||
std::shared_ptr<Subscriber> add(Fl_Widget *w) {
|
||||
cleanup();
|
||||
auto it = find(w);
|
||||
if (it == end()) {
|
||||
auto sub = std::make_shared<Subscriber>(w);
|
||||
insert(std::make_pair(w, sub));
|
||||
return sub;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
/* Remove a subscriber form the list. */
|
||||
void remove(Fl_Widget *w) {
|
||||
auto it = find(w);
|
||||
if (it != end()) {
|
||||
it->second->clear();
|
||||
erase(it);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static SubscriberList subscriber_list_;
|
||||
static std::shared_ptr<Subscriber> pushed_;
|
||||
static std::shared_ptr<Subscriber> below_pen_;
|
||||
static NSPointingDeviceType device_type_ { NSPointingDeviceTypePen };
|
||||
|
||||
struct EventData {
|
||||
double x { 0.0 };
|
||||
double y { 0.0 };
|
||||
double rx { 0.0 };
|
||||
double ry { 0.0 };
|
||||
double tilt_x { 0.0 };
|
||||
double tilt_y { 0.0 };
|
||||
double pressure { 1.0 };
|
||||
double barrel_pressure { 0.0 };
|
||||
double twist { 0.0 };
|
||||
int device_id { 0 };
|
||||
Fl::Pen::State state { (Fl::Pen::State)0 };
|
||||
Fl::Pen::State trigger { (Fl::Pen::State)0 };
|
||||
};
|
||||
|
||||
// Temporary storage of event data for the driver;
|
||||
static struct EventData ev;
|
||||
|
||||
|
||||
namespace Fl {
|
||||
|
||||
namespace Pen {
|
||||
|
||||
// double e_pressure_;
|
||||
// The event data that is made available to the user during event handling
|
||||
struct EventData e;
|
||||
|
||||
} // namespace Pen
|
||||
|
||||
@ -37,42 +124,310 @@ namespace Pen {
|
||||
|
||||
using namespace Fl::Pen;
|
||||
|
||||
bool fl_cocoa_tablet_handler(NSEvent*, Fl_Window*) { return false; }
|
||||
|
||||
Trait Fl::Pen::driver_traits() { return static_cast<Trait>(0); }
|
||||
// TODO: implement this
|
||||
Trait Fl::Pen::driver_traits() {
|
||||
return Trait::DRIVER_AVAILABLE /* | and more! */ ;
|
||||
}
|
||||
|
||||
Trait Fl::Pen::pen_traits(Fl_Window *window) { return static_cast<Trait>(0); }
|
||||
// TODO: implement this
|
||||
Trait Fl::Pen::pen_traits(Fl_Window *window) {
|
||||
return Trait::DRIVER_AVAILABLE /* | and more as we discover them! */ ;
|
||||
}
|
||||
|
||||
void Fl::Pen::subscribe(Fl_Widget* widget) { }
|
||||
void Fl::Pen::subscribe(Fl_Widget* widget) {
|
||||
subscriber_list_.add(widget);
|
||||
}
|
||||
|
||||
void Fl::Pen::unsubscribe(Fl_Widget* widget) { }
|
||||
void Fl::Pen::unsubscribe(Fl_Widget* widget) {
|
||||
subscriber_list_.remove(widget);
|
||||
}
|
||||
|
||||
void Fl::Pen::grab(Fl_Widget* widget) { }
|
||||
// TODO: implement this - do we really need this interface?
|
||||
void Fl::Pen::grab(Fl_Widget* widget) {
|
||||
}
|
||||
|
||||
void Fl::Pen::release() { }
|
||||
void Fl::Pen::release() { grab(nullptr); }
|
||||
|
||||
double Fl::Pen::event_x() { return 0.0; }
|
||||
double Fl::Pen::event_x() { return e.x; }
|
||||
|
||||
double Fl::Pen::event_y() { return 0.0; }
|
||||
double Fl::Pen::event_y() { return e.y; }
|
||||
|
||||
double Fl::Pen::event_x_root() { return 0.0; }
|
||||
double Fl::Pen::event_x_root() { return e.rx; }
|
||||
|
||||
double Fl::Pen::event_y_root() { return 0.0; }
|
||||
double Fl::Pen::event_y_root() { return e.ry; }
|
||||
|
||||
int Fl::Pen::event_id() { return 0; }
|
||||
int Fl::Pen::event_id() { return e.device_id; }
|
||||
|
||||
double Fl::Pen::event_pressure() { return 1.0; }
|
||||
double Fl::Pen::event_pressure() { return e.pressure; }
|
||||
|
||||
double Fl::Pen::event_barrel_pressure() { return 0.0; }
|
||||
double Fl::Pen::event_barrel_pressure() { return e.barrel_pressure; }
|
||||
|
||||
double Fl::Pen::event_tilt_x() { return 0.0; }
|
||||
double Fl::Pen::event_tilt_x() { return e.tilt_x; }
|
||||
|
||||
double Fl::Pen::event_tilt_y() { return 0.0; }
|
||||
double Fl::Pen::event_tilt_y() { return e.tilt_y; }
|
||||
|
||||
double Fl::Pen::event_twist() { return 0.0; }
|
||||
double Fl::Pen::event_twist() { return e.twist; }
|
||||
|
||||
// Not supported in AppKit NSEvent
|
||||
double Fl::Pen::event_proximity() { return 0.0; }
|
||||
|
||||
State Fl::Pen::event_state();
|
||||
State Fl::Pen::event_state() { return e.state; }
|
||||
|
||||
State Fl::Pen::event_trigger();
|
||||
State Fl::Pen::event_trigger() { return e.trigger; }
|
||||
|
||||
/**
|
||||
Copy the event state.
|
||||
*/
|
||||
static void copy_state() {
|
||||
Fl::Pen::State tr = (Fl::Pen::State)((uint32_t)Fl::Pen::e.state ^ (uint32_t)ev.state);
|
||||
Fl::Pen::e = ev;
|
||||
Fl::Pen::e.trigger = tr;
|
||||
Fl::e_x = (int)ev.x;
|
||||
Fl::e_y = (int)ev.y;
|
||||
Fl::e_x_root = (int)ev.rx;
|
||||
Fl::e_y_root = (int)ev.ry;
|
||||
}
|
||||
|
||||
/**
|
||||
Offset coordinates for subwindows and subsubwindows.
|
||||
*/
|
||||
static void offset_subwindow_event(Fl_Widget *w, double &x, double &y) {
|
||||
Fl_Widget *p = w, *q;
|
||||
while (p) {
|
||||
q = p->parent();
|
||||
if (p->as_window() && q) {
|
||||
x -= p->x();
|
||||
y -= p->y();
|
||||
}
|
||||
p = q;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
Check if coordinates are within the widget box.
|
||||
Coordinates are in top_window space. We iterate up the hierarchy to ensure
|
||||
that we handle subwindows correctly.
|
||||
*/
|
||||
static bool event_inside(Fl_Widget *w, double x, double y) {
|
||||
offset_subwindow_event(w, x, y);
|
||||
if (w->as_window()) {
|
||||
return ((x >= 0) && (y >= 0) && (x < w->w()) && (y < w->h()));
|
||||
} else {
|
||||
return ((x >= w->x()) && (y >= w->y()) && (x < w->x() + w->w()) && (y < w->y() + w->h()));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Find the widget under the pen event.
|
||||
Search the subscriber list for widgets that are inside the same top window,
|
||||
are visible, and are within the give coordinates. Subwindow aware.
|
||||
*/
|
||||
static Fl_Widget *find_below_pen(Fl_Window *win, double x, double y) {
|
||||
for (auto &sub: subscriber_list_) {
|
||||
Fl_Widget *candidate = sub.second->widget();
|
||||
if (candidate && (candidate->top_window() == win)) {
|
||||
if (candidate->visible() && event_inside(candidate, x, y)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
Send the current event and event data to a widget.
|
||||
Note: we will get the wrong coordinates if the widget is not a child of
|
||||
the current event window (LEAVE events between windows).
|
||||
*/
|
||||
static int pen_send(Fl_Widget *w, int event, State trigger, bool &copied) {
|
||||
// Copy most event data only once
|
||||
if (!copied) {
|
||||
copy_state();
|
||||
copied = true;
|
||||
}
|
||||
// Copy the top_window coordinates again as they may change when w changes
|
||||
e.x = ev.x;
|
||||
e.y = ev.y;
|
||||
offset_subwindow_event(w, e.x, e.y);
|
||||
Fl::e_x = e.x;
|
||||
Fl::e_y = e.y;
|
||||
// Send the event.
|
||||
e.trigger = trigger;
|
||||
return w->handle(event);
|
||||
}
|
||||
|
||||
/*
|
||||
Convert the NSEvent button number to Fl::Pen::State,
|
||||
*/
|
||||
static State button_to_trigger(NSInteger button, bool down)
|
||||
{
|
||||
switch (button) {
|
||||
case 0:
|
||||
if ( (ev.state & (State::ERASER_DOWN | State::ERASER_HOVERS)) != State::NONE ) {
|
||||
return down ? State::ERASER_DOWN : State::ERASER_HOVERS;
|
||||
} else {
|
||||
return down ? State::TIP_DOWN : State::TIP_HOVERS;
|
||||
}
|
||||
case 1: return State::BUTTON0;
|
||||
case 2: return State::BUTTON1;
|
||||
case 3: return State::BUTTON2;
|
||||
case 4: return State::BUTTON3;
|
||||
default: return State::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Handle events coming from Cocoa.
|
||||
TODO: clickCount: store in Fl::event_clicks()
|
||||
capabilityMask is useless, because it is vendor defined
|
||||
TODO: enteringProximity: TabletProximityType and Subtype
|
||||
*/
|
||||
bool fl_cocoa_tablet_handler(NSEvent *event, Fl_Window *eventWindow)
|
||||
{
|
||||
// Quick access to the main type.
|
||||
auto type = [event type];
|
||||
|
||||
// There seems nothing useful here. Ignore for now.
|
||||
if ((type == NSEventTypeMouseEntered) || (type == NSEventTypeMouseExited)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort out tablet-only events and mouse plus tablet events.
|
||||
bool is_mouse = ((type != NSEventTypeTabletPoint) && (type != NSEventTypeTabletProximity));
|
||||
|
||||
// Set the subtype if one is available. Only NSEventSubtypeTabletPoint and
|
||||
// NSEventSubtypeTabletProximity matter in this context
|
||||
NSEventSubtype subtype = is_mouse ? [event subtype] : NSEventSubtypeMouseEvent;
|
||||
|
||||
// Is this a change in proximity event?
|
||||
bool is_proximity = ((type == NSEventTypeTabletProximity) || (subtype == NSEventSubtypeTabletProximity));
|
||||
|
||||
// Is this a pen pointer event?
|
||||
bool is_point = ((type == NSEventTypeTabletPoint) || (subtype == NSEventSubtypeTabletPoint));
|
||||
|
||||
// Check if any of the pen down, move, drag, or up events was triggered.
|
||||
bool is_down = ((type == NSEventTypeLeftMouseDown) || (type == NSEventTypeRightMouseDown) || (type == NSEventTypeOtherMouseDown));
|
||||
bool is_up = ((type == NSEventTypeLeftMouseUp) || (type == NSEventTypeRightMouseUp) || (type == NSEventTypeOtherMouseUp));
|
||||
bool is_drag = ((type == NSEventTypeLeftMouseDragged) || (type == NSEventTypeRightMouseDragged) || (type == NSEventTypeOtherMouseDragged));
|
||||
bool is_motion = is_drag || (type == NSEventTypeMouseMoved);
|
||||
|
||||
// Find out if we can get the pen position
|
||||
bool has_position = (eventWindow != nullptr) && (is_up || is_down || is_motion || is_proximity || is_point);
|
||||
|
||||
// Event has extended pen data set:
|
||||
if (has_position) {
|
||||
// Get the position data.
|
||||
auto pt = [event locationInWindow];
|
||||
double s = Fl::screen_driver()->scale(0);
|
||||
ev.x = pt.x/s;
|
||||
ev.y = eventWindow->h() - pt.y/s;
|
||||
// TODO: verify actual values: root coordinates may be used for popup windows
|
||||
ev.rx = ev.x*s + eventWindow->x();
|
||||
ev.ry = ev.y*s + eventWindow->y();
|
||||
if (is_proximity) {
|
||||
// untested: use pointingDeviceID or pointingDeviceSerialNumber instead?
|
||||
ev.device_id = (int)[event vendorID];
|
||||
device_type_ = [event pointingDeviceType];
|
||||
} else {
|
||||
// Get the pressure data.
|
||||
ev.pressure = [event pressure];
|
||||
ev.barrel_pressure = [event tangentialPressure];
|
||||
// Get the tilt
|
||||
auto tilt = [event tilt];
|
||||
ev.tilt_x = -tilt.x;
|
||||
ev.tilt_y = tilt.y;
|
||||
// Other stuff
|
||||
ev.twist = [event rotation]; // TODO: untested
|
||||
}
|
||||
if (device_type_ == NSPointingDeviceTypeEraser) {
|
||||
if ([event buttonMask] & 1)
|
||||
ev.state = State::ERASER_DOWN;
|
||||
else
|
||||
ev.state = State::ERASER_HOVERS;
|
||||
} else {
|
||||
if ([event buttonMask] & 1)
|
||||
ev.state = State::TIP_DOWN;
|
||||
else
|
||||
ev.state = State::TIP_HOVERS;
|
||||
}
|
||||
if ([event buttonMask] & 0x0002) ev.state |= State::BUTTON0;
|
||||
if ([event buttonMask] & 0x0004) ev.state |= State::BUTTON1;
|
||||
if ([event buttonMask] & 0x0008) ev.state |= State::BUTTON2;
|
||||
if ([event buttonMask] & 0x0010) ev.state |= State::BUTTON3;
|
||||
// printf("0x%08x\n", [event buttonMask]);
|
||||
}
|
||||
|
||||
Fl_Widget *receiver = nullptr;
|
||||
bool pushed = false;
|
||||
bool event_data_copied = false;
|
||||
|
||||
if (has_position) {
|
||||
fl_xmousewin = eventWindow;
|
||||
if (pushed_ && pushed_->widget() && (Fl::pushed() == pushed_->widget())) {
|
||||
receiver = pushed_->widget();
|
||||
// TODO: check Fl::grab(), clear dnd_flag?
|
||||
pushed = true;
|
||||
} else {
|
||||
// TODO: check Fl::modal() and Fl::grab()
|
||||
auto bpen = below_pen_ ? below_pen_->widget() : nullptr;
|
||||
auto bmouse = Fl::belowmouse();
|
||||
auto bpen_old = bmouse && (bmouse == bpen) ? bpen : nullptr;
|
||||
auto bpen_now = find_below_pen(eventWindow, ev.x, ev.y);
|
||||
|
||||
if (bpen_now != bpen_old) {
|
||||
if (bpen_old) {
|
||||
pen_send(bpen_old, Fl::Pen::LEAVE, State::NONE, event_data_copied);
|
||||
}
|
||||
below_pen_ = nullptr;
|
||||
if (bpen_now) {
|
||||
if (pen_send(bpen_now, Fl::Pen::ENTER, State::NONE, event_data_copied)) {
|
||||
below_pen_ = subscriber_list_[bpen_now];
|
||||
Fl::belowmouse(bpen_now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
receiver = below_pen_ ? below_pen_->widget() : nullptr;
|
||||
if (!receiver)
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// TODO: handle non-position events (proximity)
|
||||
}
|
||||
|
||||
if (!receiver)
|
||||
return 0;
|
||||
|
||||
int ret = 0;
|
||||
if (is_down) {
|
||||
if (!pushed) {
|
||||
pushed_ = subscriber_list_[receiver];
|
||||
Fl::pushed(receiver);
|
||||
}
|
||||
State trigger = button_to_trigger([event buttonNumber], true);
|
||||
if ([event buttonNumber] == 0)
|
||||
ret = pen_send(receiver, Fl::Pen::TOUCH, trigger, event_data_copied);
|
||||
else
|
||||
ret = pen_send(receiver, Fl::Pen::BUTTON_PUSH, trigger, event_data_copied);
|
||||
} else if (is_up) {
|
||||
if ( (ev.state & State::ANY_DOWN) == State::NONE ) {
|
||||
Fl::pushed(nullptr);
|
||||
pushed_ = nullptr;
|
||||
}
|
||||
State trigger = button_to_trigger([event buttonNumber], true);
|
||||
if ([event buttonNumber] == 0)
|
||||
ret = pen_send(receiver, Fl::Pen::LIFT, trigger, event_data_copied);
|
||||
else
|
||||
ret = pen_send(receiver, Fl::Pen::BUTTON_RELEASE, trigger, event_data_copied);
|
||||
} else if (is_motion) {
|
||||
if (pushed) {
|
||||
ret = pen_send(receiver, Fl::Pen::DRAW, State::NONE, event_data_copied);
|
||||
} else {
|
||||
ret = pen_send(receiver, Fl::Pen::HOVER, State::NONE, event_data_copied);
|
||||
}
|
||||
}
|
||||
// Always return 1 because at this point, we capture pen events and don't
|
||||
// want mouse events anymore!
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//
|
||||
// Hello, World! program for the Fast Light Tool Kit (FLTK).
|
||||
// Penpal pen/stylus/tablet test program for the Fast Light Tool Kit (FLTK).
|
||||
//
|
||||
// Copyright 1998-2021 by Bill Spitzak and others.
|
||||
// Copyright 2025 by Bill Spitzak and others.
|
||||
//
|
||||
// This library is free software. Distribution and use rights are outlined in
|
||||
// the file "COPYING" which should have been included with this file. If this
|
||||
@ -54,8 +54,10 @@ public:
|
||||
void cv_pen_paint();
|
||||
};
|
||||
|
||||
int CanvasInterface::cv_handle(int event) {
|
||||
switch (event) {
|
||||
int CanvasInterface::cv_handle(int event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
// Event handling for pen events:
|
||||
case Fl::Pen::ENTER:
|
||||
color_++;
|
||||
@ -72,11 +74,12 @@ int CanvasInterface::cv_handle(int event) {
|
||||
overlay_ = PEN_DRAW;
|
||||
ov_x_ = Fl::event_x();
|
||||
ov_y_ = Fl::event_y();
|
||||
cv_paint();
|
||||
cv_pen_paint();
|
||||
widget_->redraw();
|
||||
return 1;
|
||||
case Fl::Pen::LIFT: return 1;
|
||||
case Fl::Pen::LEAVE: overlay_ = NONE; widget_->redraw(); return 1;
|
||||
|
||||
// Event handling for mouse events:
|
||||
case FL_ENTER:
|
||||
color_++;
|
||||
@ -133,7 +136,9 @@ void CanvasInterface::cv_draw() {
|
||||
/* fall through */
|
||||
case DRAW:
|
||||
fl_arc(ov_x_-r, ov_y_-r, 2*r, 2*r, 0, 360);
|
||||
break;
|
||||
fl_arc(ov_x_-r/2-40*Fl::Pen::event_tilt_x(),
|
||||
ov_y_-r/2-40*Fl::Pen::event_tilt_y(), r, r, 0, 360);
|
||||
printf("%d\n", Fl::Pen::event_id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -152,8 +157,9 @@ void CanvasInterface::cv_pen_paint() {
|
||||
return;
|
||||
int r = static_cast<int>(32.0 * Fl::Pen::event_pressure());
|
||||
int dx = in_window_ ? 0 : widget_->x(), dy = in_window_ ? 0 : widget_->y();
|
||||
Fl_Color cc = Fl::Pen::event_state(Fl::Pen::State::ERASER_DOWN) ? FL_WHITE : color_;
|
||||
fl_begin_offscreen(offscreen_);
|
||||
fl_draw_circle(Fl::event_x()-dx-r, Fl::event_y()-dy-r, 2*r, color_);
|
||||
fl_draw_circle(Fl::event_x()-dx-r, Fl::event_y()-dy-r, 2*r, cc);
|
||||
fl_end_offscreen();
|
||||
}
|
||||
|
||||
@ -198,7 +204,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
window->end();
|
||||
|
||||
auto cv_window = new CanvasWindow(100, 380, 200, 200);
|
||||
auto cv_window = new CanvasWindow(100, 380, 200, 200, "CV Window");
|
||||
|
||||
Fl::Pen::subscribe(canvas_widget_0);
|
||||
Fl::Pen::subscribe(canvas_widget_1);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user