Add pen/stylus/tablet API and driver for macOS (#1326)
* define the pen/tablet support API * add pen event handler stub as a fallback * add pen device test "penpal". * Add macOS pen/stylus/tablet driver. * Add Oxygen documentation.
This commit is contained in:
parent
d623ad08a9
commit
fa65cd6321
2
FL/Fl.H
2
FL/Fl.H
@ -28,6 +28,7 @@
|
|||||||
#include <FL/core/function_types.H> // widget callbacks and services
|
#include <FL/core/function_types.H> // widget callbacks and services
|
||||||
#include <FL/core/events.H> // global event handling
|
#include <FL/core/events.H> // global event handling
|
||||||
#include <FL/core/options.H> // system and application setting
|
#include <FL/core/options.H> // system and application setting
|
||||||
|
#include <FL/core/pen_events.H> // pen and tablet events
|
||||||
#include <FL/Fl_Widget_Tracker.H> // historically included here
|
#include <FL/Fl_Widget_Tracker.H> // historically included here
|
||||||
|
|
||||||
#ifdef FLTK_HAVE_CAIRO
|
#ifdef FLTK_HAVE_CAIRO
|
||||||
@ -109,7 +110,6 @@ FL_EXPORT extern bool idle();
|
|||||||
FL_EXPORT extern const char* scheme_;
|
FL_EXPORT extern const char* scheme_;
|
||||||
FL_EXPORT extern Fl_Image* scheme_bg_;
|
FL_EXPORT extern Fl_Image* scheme_bg_;
|
||||||
|
|
||||||
//FL_EXPORT extern int e_original_keysym; // late addition
|
|
||||||
FL_EXPORT extern int scrollbar_size_;
|
FL_EXPORT extern int scrollbar_size_;
|
||||||
FL_EXPORT extern int menu_linespacing_; // STR #2927
|
FL_EXPORT extern int menu_linespacing_; // STR #2927
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -76,8 +76,19 @@ class FL_EXPORT Fl_Widget_Tracker {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
Fl_Widget_Tracker(Fl_Widget *wi);
|
Fl_Widget_Tracker(Fl_Widget *wi);
|
||||||
|
// Rule of five. Note that we *can* implement these when we refactor widget
|
||||||
|
// tracking with a C++11 map or unordered_map, for example.
|
||||||
|
Fl_Widget_Tracker(const Fl_Widget_Tracker&) = delete;
|
||||||
|
Fl_Widget_Tracker(Fl_Widget_Tracker&&) = delete;
|
||||||
|
Fl_Widget_Tracker& operator=(const Fl_Widget_Tracker&) = delete;
|
||||||
|
Fl_Widget_Tracker& operator=(Fl_Widget_Tracker&&) = delete;
|
||||||
~Fl_Widget_Tracker();
|
~Fl_Widget_Tracker();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Clear the widget pointer.
|
||||||
|
*/
|
||||||
|
void clear() { wp_ = nullptr; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns a pointer to the watched widget.
|
Returns a pointer to the watched widget.
|
||||||
\return nullptr if the widget was deleted.
|
\return nullptr if the widget was deleted.
|
||||||
@ -88,13 +99,13 @@ public:
|
|||||||
Check if the widget was deleted since the tracker was created.
|
Check if the widget was deleted since the tracker was created.
|
||||||
\return 1 if the watched widget has been deleted, otherwise 0
|
\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.
|
Check if the widget exists and was not deleted since the tracker was created.
|
||||||
\return 1 if the watched widget exists, otherwise 0
|
\return 1 if the watched widget exists, otherwise 0
|
||||||
*/
|
*/
|
||||||
int exists() { return wp_ != 0; }
|
int exists() { return wp_ != nullptr; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -71,6 +71,8 @@ FL_EXPORT extern Fl_Widget* belowmouse_; ///< Widget under mouse
|
|||||||
FL_EXPORT extern Fl_Widget* pushed_; ///< Widget receiving drag events
|
FL_EXPORT extern Fl_Widget* pushed_; ///< Widget receiving drag events
|
||||||
FL_EXPORT extern Fl_Widget* focus_; ///< Widget with keyboard focus
|
FL_EXPORT extern Fl_Widget* focus_; ///< Widget with keyboard focus
|
||||||
|
|
||||||
|
// Event variables should be private, but would harm back compatibility.
|
||||||
|
|
||||||
#endif // FL_DOXYGEN
|
#endif // FL_DOXYGEN
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
465
FL/core/pen_events.H
Normal file
465
FL/core/pen_events.H
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
//
|
||||||
|
// Pen event header file for the Fast Light Tool Kit (FLTK).
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// file is missing or damaged, see the license at:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/COPYING.php
|
||||||
|
//
|
||||||
|
// Please see the following page on how to report bugs and issues:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/bugs.php
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
\file FL/core/pen_events.H
|
||||||
|
\brief Pen event handling variables and functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef Fl_core_pen_events_H
|
||||||
|
#define Fl_core_pen_events_H
|
||||||
|
|
||||||
|
#include <FL/fl_config.h> // build configuration
|
||||||
|
#include <FL/Fl_Export.H> // for FL_EXPORT
|
||||||
|
#include <FL/core/function_types.H> // widget callbacks and services
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class Fl_Widget;
|
||||||
|
|
||||||
|
namespace Fl {
|
||||||
|
|
||||||
|
/** FLTK Pen/Stylus/Tablet input driver API. */
|
||||||
|
namespace Pen {
|
||||||
|
|
||||||
|
/**
|
||||||
|
\defgroup fl_pen_events Pen and tablet event handling
|
||||||
|
\ingroup fl_events
|
||||||
|
\brief This chapter documents the Fl::Pen namespace API, declared in <FL/core/pen_events.H>
|
||||||
|
|
||||||
|
The FL::Pen namespace contains everything needed to work with a pen type input
|
||||||
|
device, either in connection with an external tablet, or as a stylus for
|
||||||
|
drawing directly onto a screen.
|
||||||
|
|
||||||
|
To receive pen input, call Fl::Pen::subscribe() for one or more widgets. The
|
||||||
|
widget will receive a Fl::Pen::ENTER event when the stylus enters the widget
|
||||||
|
area. By returning 1 to Fl::Pen::ENTER, all further pen events are sent to
|
||||||
|
this widget, and no mouse events are generated until Fl::Pen::LEAVE.
|
||||||
|
|
||||||
|
Returning 0 Fl::Pen::ENTER tells FLTK to suppress further pen events until
|
||||||
|
Fl::Pen::LEAVE, and convert them into mouse events instead.
|
||||||
|
|
||||||
|
Pen events also set Fl::event_x(), Fl::event_y(), Fl::event_x_root(),
|
||||||
|
Fl::event_y_root(), Fl::event_is_click(), and Fl::event_clicks().
|
||||||
|
|
||||||
|
@{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Bitfield of traits.
|
||||||
|
This is used in Fl::Pen::driver_traits() and Fl::Pen::pen_traits().
|
||||||
|
*/
|
||||||
|
enum class Trait : uint32_t {
|
||||||
|
/// No bits set
|
||||||
|
NONE = 0x0000,
|
||||||
|
/// Set if FLTK supports tablets and pens on this platform
|
||||||
|
DRIVER_AVAILABLE = 0x0001,
|
||||||
|
/// Set after the system detected a pen, stylus, or tablet. This bit may not be
|
||||||
|
/// set until a pen is brought into proximity of the tablet.
|
||||||
|
DETECTED = 0x0002,
|
||||||
|
/// If set, this is a digitizer for a display; if clear, this is a standalone tablet
|
||||||
|
DISPLAY = 0x0004,
|
||||||
|
/// Driver provides different device IDs for different pens
|
||||||
|
PEN_ID = 0x0008,
|
||||||
|
/// Pen may have an eraser tip
|
||||||
|
ERASER = 0x0010,
|
||||||
|
/// Pen returns a pressure value
|
||||||
|
PRESSURE = 0x0020,
|
||||||
|
/// Pen returns a barrel pressure value (tangential pressure)
|
||||||
|
BARREL_PRESSURE = 0x0040,
|
||||||
|
/// Pen returns tilt in X direction
|
||||||
|
TILT_X = 0x0080,
|
||||||
|
/// Pen returns tilt in Y direction
|
||||||
|
TILT_Y = 0x0100,
|
||||||
|
/// Pen returns a twist value
|
||||||
|
TWIST = 0x0200,
|
||||||
|
/// Pen returns a proximity value
|
||||||
|
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
|
||||||
|
TIP_DOWN = 0x0002,
|
||||||
|
/// The eraser hovers over the surface but does not touch it
|
||||||
|
ERASER_HOVERS = 0x0004,
|
||||||
|
/// The eraser touches the surface
|
||||||
|
ERASER_DOWN = 0x0008,
|
||||||
|
/// Barrel button 0, usually the lower button on a pen, is pressed
|
||||||
|
BUTTON0 = 0x0100,
|
||||||
|
/// Barrel button 1, usually the upper button on a pen, is pressed
|
||||||
|
BUTTON1 = 0x0200,
|
||||||
|
/// Barrel button 2 is pressed
|
||||||
|
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.
|
||||||
|
\see enum Fl_Event
|
||||||
|
*/
|
||||||
|
enum Event {
|
||||||
|
/**
|
||||||
|
Pen entered the proximity of the tablet with a new pen.
|
||||||
|
*/
|
||||||
|
DETECTED = 0x1000,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pen entered the proximity of the tablet with a known, but changed pen.
|
||||||
|
User changed to a different pen (event_id() > 0) or the pen or tablet
|
||||||
|
was disconnected (event_id() == -1). Pen IDs, if supported, are assigned by
|
||||||
|
the tablet manufacturer.
|
||||||
|
*/
|
||||||
|
CHANGED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pen entered the proximity of the tablet with a known pen.
|
||||||
|
*/
|
||||||
|
IN_RANGE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pen left the proximity of the tablet.
|
||||||
|
*/
|
||||||
|
OUT_OF_RANGE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pen entered the widget area, either by moving in x/y, or by
|
||||||
|
a proximity change (pen gets closer to the surface).
|
||||||
|
event_trigger() returns 0, TIP_HOVERS, or ERASER_HOVERS.
|
||||||
|
*/
|
||||||
|
ENTER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
If no button is pressed, indicates that the pen left the widget area.
|
||||||
|
While any pen button is held down, or the pen touches the surface,
|
||||||
|
Fl::pushed() is set, and the pushed widgets receives DRAG events, even
|
||||||
|
if the pen leaves the widget area. If all buttons are released outside the
|
||||||
|
widget area, a LEAVE event is sent as well as LIFT or BUTTON_RELEASE.
|
||||||
|
*/
|
||||||
|
LEAVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pen went from hovering to touching the surface.
|
||||||
|
event_trigger() returns TIP_DOWN or ERASER_DOWN.
|
||||||
|
*/
|
||||||
|
TOUCH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pen went from touching to hovering over the surface.
|
||||||
|
event_trigger() returns TIP_HOVERS or ERASER_HOVERS.
|
||||||
|
*/
|
||||||
|
LIFT,
|
||||||
|
|
||||||
|
/** Pen moved without touching the surface and no button is pressed. */
|
||||||
|
HOVER,
|
||||||
|
|
||||||
|
/** Pen moved while touching the surface, or any button is pressed. */
|
||||||
|
DRAW,
|
||||||
|
|
||||||
|
/**
|
||||||
|
A pen button was pushed.
|
||||||
|
event_trigger() returns BUTTON0, BUTTON1, BUTTON2, or BUTTON3.
|
||||||
|
*/
|
||||||
|
BUTTON_PUSH,
|
||||||
|
|
||||||
|
/**
|
||||||
|
A pen button was released.
|
||||||
|
event_trigger() returns BUTTON0, BUTTON1, BUTTON2, or BUTTON3.
|
||||||
|
*/
|
||||||
|
BUTTON_RELEASE
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Query the traits supported by the pen/tablet driver.
|
||||||
|
|
||||||
|
This function returns a bitfield of traits that are supported by the FLTK driver
|
||||||
|
for this platform. If a trait is not supported, the corresponding event value
|
||||||
|
will not return a useful value. Note that even if the FLTK driver support a
|
||||||
|
trait, the underlying pen device or driver may not. Fl::Pen will return a
|
||||||
|
known default for those event values.
|
||||||
|
|
||||||
|
The bitfield returned is static.
|
||||||
|
|
||||||
|
\return a bitfield of supported traits
|
||||||
|
\see pen_traits()
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern Trait driver_traits();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Return true if the corresponding bit is set in the driver traits.
|
||||||
|
\param[in] bits check for one or more trait bits
|
||||||
|
\return true if any bit is set
|
||||||
|
*/
|
||||||
|
inline bool driver_traits(Trait bits) {
|
||||||
|
return ((driver_traits() & bits) != Trait::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Query traits of the current pen or stylus.
|
||||||
|
The value returned by this function may change when pens change or when more
|
||||||
|
information becomes known about the currently used pen.
|
||||||
|
\param[in] pen_id a now pen ID as returned from event_pen_id(),
|
||||||
|
or 0 for the current pen
|
||||||
|
\return a bitfield of supported traits
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern Trait pen_traits(int pen_id = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Return true if the corresponding bit is set in the pen traits.
|
||||||
|
\param[in] bits check for one or more trait bits
|
||||||
|
\param[in] pen_id a now pen ID as returned from event_pen_id(),
|
||||||
|
or 0 for the current pen
|
||||||
|
\return true if any bit is set
|
||||||
|
*/
|
||||||
|
inline bool pen_traits(Trait bits, int pen_id = 0) {
|
||||||
|
return ((pen_traits() & bits) != Trait::NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Receive a Pen::ENTER event when the pen moves inside this widget.
|
||||||
|
Multiple widgets can subscribe to pen events, but every widget must only
|
||||||
|
subscribe once.
|
||||||
|
\param widget Widget to subscribe to pen events
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern void subscribe(Fl_Widget* widget);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Stop receiving Pen::ENTER for this widget.
|
||||||
|
Deleting a widget will automatically unsubscribe it.
|
||||||
|
\param widget Widget to unsubscribe from pen events
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern void unsubscribe(Fl_Widget* widget);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Clear the "pushed" state and forward pen events as mouse events.
|
||||||
|
Call this if another window is popped up during pen event handling, so
|
||||||
|
mouse event handling can resume normal.
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern void release();
|
||||||
|
|
||||||
|
/// \name Query values during event handling
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the pen x and y position inside the handling widget as doubles.
|
||||||
|
These functions provide high-precision pen coordinates relative to the widget
|
||||||
|
that received the pen event. For integer coordinates, use Fl::event_x() and
|
||||||
|
Fl::event_y() instead.
|
||||||
|
\return Pen position as floating-point coordinate, defaults to 0.0
|
||||||
|
\see Fl::event_x(), Fl::event_y()
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_x();
|
||||||
|
/** \brief Returns pen Y coordinate in widget space, see event_x(). */
|
||||||
|
FL_EXPORT extern double event_y();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the pen x and y position in global coordinates as doubles.
|
||||||
|
For integer coordinates, use Fl::event_x_root() and Fl::event_y_root().
|
||||||
|
\return Pen position as floating-point coordinate in screen space, defaults to 0.0
|
||||||
|
\see Fl::event_x_root(), Fl::event_y_root()
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_x_root();
|
||||||
|
/** \brief Returns pen Y coordinate in screen space, see event_x_root(). */
|
||||||
|
FL_EXPORT extern double event_y_root();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the ID of the pen used in the last event.
|
||||||
|
\return Unique pen identifier, or -1 if pen was removed, defaults to 0
|
||||||
|
\see Trait::PEN_ID
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern int event_pen_id();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the pressure between the tip or eraser and the surface.
|
||||||
|
\return pressure value from 0.0 (no pressure) to 1.0 (maximum pressure),
|
||||||
|
defaults to 1.0.
|
||||||
|
\see Trait::PRESSURE
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_pressure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns barrel pressure or tangential pressure.
|
||||||
|
\return Pressure value from -1.0 to 1.0 , defaults to 0.0 .
|
||||||
|
\see Trait::BARREL_PRESSURE
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_barrel_pressure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the tilt of the pen in the x and y directions between -1 and 1.
|
||||||
|
|
||||||
|
X-axis tilt returns -1.0 when the pen tilts all the way to the left, 0.0 when
|
||||||
|
it is perfectly vertical, and 1.0 all the way to the right. Most pens seem to
|
||||||
|
return a maximum range of -0.7 to 0.7.
|
||||||
|
|
||||||
|
Y-axis tilt returns -1.0 when the pen tilts away from the user, and 1.0 when
|
||||||
|
it tilts toward the user.
|
||||||
|
|
||||||
|
\return Tilt value from -1.0 to 1.0, defaults to 0.0
|
||||||
|
\see Trait::TILT_X, Trait::TILT_Y
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_tilt_x();
|
||||||
|
/** \brief Returns pen Y-axis tilt, see event_tilt_x() */
|
||||||
|
FL_EXPORT extern double event_tilt_y();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the pens axial rotation in degrees.
|
||||||
|
\return Twist angle in degrees, defaults to 0.0 .
|
||||||
|
\see Trait::TWIST
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_twist();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the proximity of the pen to the surface between 0 and 1.
|
||||||
|
A proximity of 0 is closest to the surface, 1 is farthest away.
|
||||||
|
\return Proximity value from 0.0 (touching) to 1.0 (far away), defaults to 0.0 .
|
||||||
|
\see Trait::PROXIMITY
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern double event_proximity();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Returns the state of the various buttons and tips.
|
||||||
|
\return Current state flags (combination of State values)
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern State event_state();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Return true if the corresponding bit is set in the event state.
|
||||||
|
\param[in] bits check for one or more event state bits
|
||||||
|
\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
|
||||||
|
*/
|
||||||
|
FL_EXPORT extern State event_trigger();
|
||||||
|
|
||||||
|
/** @} */ // group fl_pen_events
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Pen
|
||||||
|
|
||||||
|
} // namespace Fl
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Resources:
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
1. Legacy WinTab API (Win2k), Wintab32.dll, wintab.h
|
||||||
|
https://developer.wacom.com/en-us/developer-dashboard/downloads
|
||||||
|
2. Windows Ink API (Modern, Win10), Windows.UI.Input.Inking (WinRT API), InkCanvas(), etc.
|
||||||
|
https://learn.microsoft.com/windows/uwp/design/input/windows-ink
|
||||||
|
3. Pointer Input / WM_POINTER API (Win8), WM_POINTERUPDATE, GetPointerPenInfo
|
||||||
|
https://learn.microsoft.com/windows/win32/inputmsg/wm-pointerupdate
|
||||||
|
return WTInfo(0, 0, NULL) > 0; // Wintab check
|
||||||
|
|
||||||
|
Linux:
|
||||||
|
1. Low-level: evdev, /dev/input/event*, libevdev,
|
||||||
|
https://www.kernel.org/doc/html/latest/input/event-codes.html
|
||||||
|
2. Mid-level: XInput2 (for X11), XI_Motion, XI_ButtonPress
|
||||||
|
https://www.x.org/releases/current/doc/inputproto/XI2proto.txt
|
||||||
|
https://www.freedesktop.org/wiki/Software/libevdev/
|
||||||
|
3. Mid-level: Wayland tablet protocol, tablet-v2 protocol,
|
||||||
|
zwp_tablet_tool_v2_listener, zwp_tablet_v2, zwp_tablet_seat_v2
|
||||||
|
https://wayland.app/protocols/tablet-v2
|
||||||
|
|
||||||
|
SDL3:
|
||||||
|
https://github.com/libsdl-org/SDL/blob/main/include/SDL3/SDL_pen.h
|
||||||
|
https://wiki.libsdl.org/SDL3/CategoryPen
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#endif // !Fl_core_pen_events_H
|
||||||
83
FL/names.h
83
FL/names.h
@ -24,6 +24,9 @@
|
|||||||
#ifndef FL_NAMES_H
|
#ifndef FL_NAMES_H
|
||||||
#define FL_NAMES_H
|
#define FL_NAMES_H
|
||||||
|
|
||||||
|
#include <FL/Fl.H> // for event constants
|
||||||
|
#include <map>
|
||||||
|
|
||||||
/** \defgroup fl_events Events handling functions
|
/** \defgroup fl_events Events handling functions
|
||||||
@{
|
@{
|
||||||
*/
|
*/
|
||||||
@ -43,43 +46,53 @@
|
|||||||
}
|
}
|
||||||
\endcode
|
\endcode
|
||||||
*/
|
*/
|
||||||
const char * const fl_eventnames[] =
|
std::map<int, const char*> fl_eventnames = {
|
||||||
{
|
{ FL_NO_EVENT, "FL_NO_EVENT" },
|
||||||
"FL_NO_EVENT",
|
{ FL_PUSH, "FL_PUSH" },
|
||||||
"FL_PUSH",
|
{ FL_RELEASE, "FL_RELEASE" },
|
||||||
"FL_RELEASE",
|
{ FL_ENTER, "FL_ENTER" },
|
||||||
"FL_ENTER",
|
{ FL_LEAVE, "FL_LEAVE" },
|
||||||
"FL_LEAVE",
|
{ FL_DRAG, "FL_DRAG" },
|
||||||
"FL_DRAG",
|
{ FL_FOCUS, "FL_FOCUS" },
|
||||||
"FL_FOCUS",
|
{ FL_UNFOCUS, "FL_UNFOCUS" },
|
||||||
"FL_UNFOCUS",
|
{ FL_KEYDOWN, "FL_KEYDOWN" },
|
||||||
"FL_KEYDOWN",
|
{ FL_KEYUP, "FL_KEYUP" },
|
||||||
"FL_KEYUP",
|
{ FL_CLOSE, "FL_CLOSE" },
|
||||||
"FL_CLOSE",
|
{ FL_MOVE, "FL_MOVE" },
|
||||||
"FL_MOVE",
|
{ FL_SHORTCUT, "FL_SHORTCUT" },
|
||||||
"FL_SHORTCUT",
|
{ FL_DEACTIVATE, "FL_DEACTIVATE" },
|
||||||
"FL_DEACTIVATE",
|
{ FL_ACTIVATE, "FL_ACTIVATE" },
|
||||||
"FL_ACTIVATE",
|
{ FL_HIDE, "FL_HIDE" },
|
||||||
"FL_HIDE",
|
{ FL_SHOW, "FL_SHOW" },
|
||||||
"FL_SHOW",
|
{ FL_PASTE, "FL_PASTE" },
|
||||||
"FL_PASTE",
|
{ FL_SELECTIONCLEAR, "FL_SELECTIONCLEAR" },
|
||||||
"FL_SELECTIONCLEAR",
|
{ FL_MOUSEWHEEL, "FL_MOUSEWHEEL" },
|
||||||
"FL_MOUSEWHEEL",
|
{ FL_DND_ENTER, "FL_DND_ENTER" },
|
||||||
"FL_DND_ENTER",
|
{ FL_DND_DRAG, "FL_DND_DRAG" },
|
||||||
"FL_DND_DRAG",
|
{ FL_DND_LEAVE, "FL_DND_LEAVE" },
|
||||||
"FL_DND_LEAVE",
|
{ FL_DND_RELEASE, "FL_DND_RELEASE" },
|
||||||
"FL_DND_RELEASE",
|
{ FL_SCREEN_CONFIGURATION_CHANGED, "FL_SCREEN_CONFIGURATION_CHANGED" },
|
||||||
"FL_SCREEN_CONFIGURATION_CHANGED",
|
{ FL_FULLSCREEN, "FL_FULLSCREEN" },
|
||||||
"FL_FULLSCREEN",
|
{ FL_ZOOM_GESTURE, "FL_ZOOM_GESTURE" },
|
||||||
"FL_ZOOM_GESTURE",
|
{ FL_ZOOM_EVENT, "FL_ZOOM_EVENT" },
|
||||||
"FL_ZOOM_EVENT",
|
{ FL_BEFORE_TOOLTIP, "FL_BEFORE_TOOLTIP" },
|
||||||
"FL_BEFORE_TOOLTIP",
|
{ FL_BEFORE_MENU, "FL_BEFORE_MENU" },
|
||||||
"FL_BEFORE_MENU",
|
{ /*FL_EVENT_*/ 30, "FL_EVENT_30" }, // not yet defined, just in case it /will/ be defined ...
|
||||||
"FL_EVENT_30", // not yet defined, just in case it /will/ be defined ...
|
{ /*FL_EVENT_*/ 31, "FL_EVENT_31" }, // not yet defined, just in case it /will/ be defined ...
|
||||||
"FL_EVENT_31", // not yet defined, just in case it /will/ be defined ...
|
{ /*FL_EVENT_*/ 32, "FL_EVENT_32" }, // not yet defined, just in case it /will/ be defined ...
|
||||||
"FL_EVENT_32" // not yet defined, just in case it /will/ be defined ...
|
{ Fl::Pen::DETECTED, "Fl::Pen::DETECTED" },
|
||||||
|
{ Fl::Pen::CHANGED, "Fl::Pen::CHANGED" },
|
||||||
|
{ Fl::Pen::ENTER, "Fl::Pen::ENTER" },
|
||||||
|
{ Fl::Pen::LEAVE, "Fl::Pen::LEAVE" },
|
||||||
|
{ Fl::Pen::TOUCH, "Fl::Pen::TOUCH" },
|
||||||
|
{ Fl::Pen::LIFT, "Fl::Pen::LIFT" },
|
||||||
|
{ Fl::Pen::HOVER, "Fl::Pen::HOVER" },
|
||||||
|
{ Fl::Pen::DRAW, "Fl::Pen::DRAW" },
|
||||||
|
{ Fl::Pen::BUTTON_PUSH, "Fl::Pen::BUTTON_PUSH" },
|
||||||
|
{ Fl::Pen::BUTTON_RELEASE, "Fl::Pen::BUTTON_RELEASE" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This is an array of font names you can use to convert font numbers into names.
|
This is an array of font names you can use to convert font numbers into names.
|
||||||
|
|
||||||
|
|||||||
@ -202,6 +202,12 @@ file(GLOB
|
|||||||
"*.[hH]"
|
"*.[hH]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# find all private header files in source directory "src/..."
|
||||||
|
file(GLOB
|
||||||
|
PRIVATE_HEADER_FILES
|
||||||
|
"*.[hH]"
|
||||||
|
)
|
||||||
|
|
||||||
# add generated header files in build directory
|
# add generated header files in build directory
|
||||||
list(APPEND HEADER_FILES
|
list(APPEND HEADER_FILES
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/../FL/fl_config.h
|
${CMAKE_CURRENT_BINARY_DIR}/../FL/fl_config.h
|
||||||
@ -235,6 +241,7 @@ if(FLTK_USE_X11 AND NOT FLTK_USE_WAYLAND)
|
|||||||
drivers/Xlib/Fl_Xlib_Copy_Surface_Driver.cxx
|
drivers/Xlib/Fl_Xlib_Copy_Surface_Driver.cxx
|
||||||
drivers/Xlib/Fl_Xlib_Image_Surface_Driver.cxx
|
drivers/Xlib/Fl_Xlib_Image_Surface_Driver.cxx
|
||||||
drivers/X11/fl_X11_platform_init.cxx
|
drivers/X11/fl_X11_platform_init.cxx
|
||||||
|
drivers/Stubs/Fl_Stubs_Pen_Events.cxx
|
||||||
Fl_x.cxx
|
Fl_x.cxx
|
||||||
fl_dnd_x.cxx
|
fl_dnd_x.cxx
|
||||||
Fl_Native_File_Chooser_FLTK.cxx
|
Fl_Native_File_Chooser_FLTK.cxx
|
||||||
@ -311,6 +318,7 @@ elseif(FLTK_USE_WAYLAND)
|
|||||||
drivers/Wayland/fl_wayland_clipboard_dnd.cxx
|
drivers/Wayland/fl_wayland_clipboard_dnd.cxx
|
||||||
drivers/Wayland/fl_wayland_platform_init.cxx
|
drivers/Wayland/fl_wayland_platform_init.cxx
|
||||||
drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx
|
drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx
|
||||||
|
drivers/Stubs/Fl_Stubs_Pen_Events.cxx
|
||||||
Fl_Native_File_Chooser_FLTK.cxx
|
Fl_Native_File_Chooser_FLTK.cxx
|
||||||
Fl_Native_File_Chooser_GTK.cxx
|
Fl_Native_File_Chooser_GTK.cxx
|
||||||
)
|
)
|
||||||
@ -397,6 +405,7 @@ else()
|
|||||||
drivers/GDI/Fl_GDI_Graphics_Driver_vertex.cxx
|
drivers/GDI/Fl_GDI_Graphics_Driver_vertex.cxx
|
||||||
drivers/GDI/Fl_GDI_Copy_Surface_Driver.cxx
|
drivers/GDI/Fl_GDI_Copy_Surface_Driver.cxx
|
||||||
drivers/GDI/Fl_GDI_Image_Surface_Driver.cxx
|
drivers/GDI/Fl_GDI_Image_Surface_Driver.cxx
|
||||||
|
drivers/Stubs/Fl_Stubs_Pen_Events.cxx
|
||||||
Fl_win32.cxx
|
Fl_win32.cxx
|
||||||
fl_dnd_win32.cxx
|
fl_dnd_win32.cxx
|
||||||
Fl_Native_File_Chooser_WIN32.cxx
|
Fl_Native_File_Chooser_WIN32.cxx
|
||||||
@ -617,6 +626,7 @@ if(APPLE AND NOT FLTK_BACKEND_X11)
|
|||||||
set(MMFILES
|
set(MMFILES
|
||||||
Fl_cocoa.mm
|
Fl_cocoa.mm
|
||||||
drivers/Cocoa/Fl_Cocoa_Printer_Driver.mm
|
drivers/Cocoa/Fl_Cocoa_Printer_Driver.mm
|
||||||
|
drivers/Cocoa/Fl_Cocoa_Pen_Events.mm
|
||||||
Fl_Native_File_Chooser_MAC.mm
|
Fl_Native_File_Chooser_MAC.mm
|
||||||
Fl_MacOS_Sys_Menu_Bar.mm
|
Fl_MacOS_Sys_Menu_Bar.mm
|
||||||
)
|
)
|
||||||
|
|||||||
@ -886,7 +886,8 @@ int menuwindow::handle_part1(int e) {
|
|||||||
int item; const Fl_Menu_Item* m = mw.menu->find_shortcut(&item);
|
int item; const Fl_Menu_Item* m = mw.menu->find_shortcut(&item);
|
||||||
if (m) {
|
if (m) {
|
||||||
setitem(m, mymenu, item);
|
setitem(m, mymenu, item);
|
||||||
if (!m->submenu()) pp.state = DONE_STATE;
|
if (!m->submenu())
|
||||||
|
pp.state = DONE_STATE;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,6 +72,10 @@ extern int fl_send_system_handlers(void *e);
|
|||||||
// converting cr lf converter function
|
// converting cr lf converter function
|
||||||
static void createAppleMenu(void);
|
static void createAppleMenu(void);
|
||||||
static void cocoaMouseHandler(NSEvent *theEvent);
|
static void cocoaMouseHandler(NSEvent *theEvent);
|
||||||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
|
||||||
|
static bool cocoaTabletHandler(NSEvent *theEvent, bool lock);
|
||||||
|
extern bool fl_cocoa_tablet_handler(NSEvent*, Fl_Window*);
|
||||||
|
#endif
|
||||||
static void clipboard_check(void);
|
static void clipboard_check(void);
|
||||||
static NSBitmapImageRep* rect_to_NSBitmapImageRep(Fl_Window *win, int x, int y, int w, int h);
|
static NSBitmapImageRep* rect_to_NSBitmapImageRep(Fl_Window *win, int x, int y, int w, int h);
|
||||||
static NSBitmapImageRep* rect_to_NSBitmapImageRep_subwins(Fl_Window *win, int x, int y, int w, int h, bool capture_subwins);
|
static NSBitmapImageRep* rect_to_NSBitmapImageRep_subwins(Fl_Window *win, int x, int y, int w, int h, bool capture_subwins);
|
||||||
@ -627,6 +631,10 @@ void Fl_Cocoa_Screen_Driver::breakMacEventLoop()
|
|||||||
endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation;
|
endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation;
|
||||||
#endif
|
#endif
|
||||||
- (BOOL)did_view_resolution_change;
|
- (BOOL)did_view_resolution_change;
|
||||||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
|
||||||
|
- (void)tabletProximity:(NSEvent *)theEvent;
|
||||||
|
- (void)tabletPoint:(NSEvent *)theEvent;
|
||||||
|
#endif
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14
|
||||||
- (void)create_aux_bitmap:(CGContextRef)gc retina:(BOOL)r;
|
- (void)create_aux_bitmap:(CGContextRef)gc retina:(BOOL)r;
|
||||||
- (void)reset_aux_bitmap;
|
- (void)reset_aux_bitmap;
|
||||||
@ -1049,21 +1057,52 @@ static void cocoaMagnifyHandler(NSEvent *theEvent)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool cocoaTabletHandler(NSEvent *theEvent, bool lock)
|
||||||
|
{
|
||||||
|
if (lock) fl_lock_function();
|
||||||
|
auto theWindow = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
|
||||||
|
auto ret = fl_cocoa_tablet_handler(theEvent, theWindow);
|
||||||
|
if (lock) fl_unlock_function();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Fl {
|
||||||
|
// Global mouse position at mouse down event
|
||||||
|
int e_x_down { 0 };
|
||||||
|
int e_y_down { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cocoa Mouse Button Handler
|
* Cocoa Mouse Button Handler
|
||||||
*/
|
*/
|
||||||
static void cocoaMouseHandler(NSEvent *theEvent)
|
static void cocoaMouseHandler(NSEvent *theEvent)
|
||||||
{
|
{
|
||||||
static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2, FL_Button+4, FL_Button+5 };
|
static int keysym[] = { 0, FL_Button+1, FL_Button+3, FL_Button+2, FL_Button+4, FL_Button+5 };
|
||||||
static int px, py;
|
|
||||||
|
|
||||||
fl_lock_function();
|
fl_lock_function();
|
||||||
|
|
||||||
|
// Handle tablet proximity and point subevents
|
||||||
|
if ( ([theEvent type] != NSEventTypeMouseEntered) // does not have a subtype
|
||||||
|
&& ([theEvent type] != NSEventTypeMouseExited) ) // does not have a subtype
|
||||||
|
{
|
||||||
|
if ( ([theEvent subtype] == NSEventSubtypeTabletPoint)
|
||||||
|
|| ([theEvent subtype] == NSEventSubtypeTabletProximity) )
|
||||||
|
{
|
||||||
|
if (cocoaTabletHandler(theEvent, false)) {
|
||||||
|
fl_unlock_function();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// else fall through into mouse event handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
|
Fl_Window *window = (Fl_Window*)[(FLWindow*)[theEvent window] getFl_Window];
|
||||||
if (!window || !window->shown() ) {
|
if (!window || !window->shown() ) {
|
||||||
fl_unlock_function();
|
fl_unlock_function();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSPoint pos = [theEvent locationInWindow];
|
NSPoint pos = [theEvent locationInWindow];
|
||||||
float s = Fl::screen_driver()->scale(0);
|
float s = Fl::screen_driver()->scale(0);
|
||||||
pos.x /= s; pos.y /= s;
|
pos.x /= s; pos.y /= s;
|
||||||
@ -1096,7 +1135,8 @@ static void cocoaMouseHandler(NSEvent *theEvent)
|
|||||||
case NSEventTypeOtherMouseDown:
|
case NSEventTypeOtherMouseDown:
|
||||||
sendEvent = FL_PUSH;
|
sendEvent = FL_PUSH;
|
||||||
Fl::e_is_click = 1;
|
Fl::e_is_click = 1;
|
||||||
px = (int)pos.x; py = (int)pos.y;
|
Fl::e_x_down = (int)pos.x;
|
||||||
|
Fl::e_y_down = (int)pos.y;
|
||||||
if ([theEvent clickCount] > 1)
|
if ([theEvent clickCount] > 1)
|
||||||
Fl::e_clicks++;
|
Fl::e_clicks++;
|
||||||
else
|
else
|
||||||
@ -1121,7 +1161,8 @@ static void cocoaMouseHandler(NSEvent *theEvent)
|
|||||||
case NSEventTypeOtherMouseDragged: {
|
case NSEventTypeOtherMouseDragged: {
|
||||||
if ( !sendEvent ) {
|
if ( !sendEvent ) {
|
||||||
sendEvent = FL_MOVE; // Fl::handle will convert into FL_DRAG
|
sendEvent = FL_MOVE; // Fl::handle will convert into FL_DRAG
|
||||||
if (fabs(pos.x-px)>5 || fabs(pos.y-py)>5)
|
if ( (fabs(pos.x - Fl::e_x_down) > 5) ||
|
||||||
|
(fabs(pos.y - Fl::e_y_down) > 5))
|
||||||
Fl::e_is_click = 0;
|
Fl::e_is_click = 0;
|
||||||
}
|
}
|
||||||
mods_to_e_state( mods );
|
mods_to_e_state( mods );
|
||||||
@ -1158,6 +1199,7 @@ static void cocoaMouseHandler(NSEvent *theEvent)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@interface FLTextView : NSTextView // this subclass is only needed under OS X < 10.6
|
@interface FLTextView : NSTextView // this subclass is only needed under OS X < 10.6
|
||||||
{
|
{
|
||||||
BOOL isActive;
|
BOOL isActive;
|
||||||
@ -1847,7 +1889,7 @@ void Fl_Darwin_System_Driver::open_callback(void (*cb)(const char *)) {
|
|||||||
// still needed for the system menu.
|
// still needed for the system menu.
|
||||||
[[NSApp keyWindow] sendEvent:theEvent];
|
[[NSApp keyWindow] sendEvent:theEvent];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[NSApp sendEvent:theEvent];
|
[NSApp sendEvent:theEvent];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
@ -2589,6 +2631,14 @@ static FLTextInputContext* fltextinputcontext_instance = nil;
|
|||||||
- (void)mouseExited:(NSEvent *)theEvent {
|
- (void)mouseExited:(NSEvent *)theEvent {
|
||||||
cocoaMouseHandler(theEvent);
|
cocoaMouseHandler(theEvent);
|
||||||
}
|
}
|
||||||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
|
||||||
|
- (void)tabletProximity:(NSEvent *)theEvent {
|
||||||
|
cocoaTabletHandler(theEvent, true);
|
||||||
|
}
|
||||||
|
- (void)tabletPoint:(NSEvent *)theEvent {
|
||||||
|
cocoaTabletHandler(theEvent, true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
|
||||||
- (void)updateTrackingAreas {
|
- (void)updateTrackingAreas {
|
||||||
if (fl_mac_os_version >= 100500) {
|
if (fl_mac_os_version >= 100500) {
|
||||||
|
|||||||
@ -27,5 +27,6 @@
|
|||||||
|
|
||||||
void Fl::grab(Fl_Window *win)
|
void Fl::grab(Fl_Window *win)
|
||||||
{
|
{
|
||||||
|
Fl::Pen::release();
|
||||||
screen_driver()->grab(win);
|
screen_driver()->grab(win);
|
||||||
}
|
}
|
||||||
|
|||||||
530
src/drivers/Cocoa/Fl_Cocoa_Pen_Events.mm
Normal file
530
src/drivers/Cocoa/Fl_Cocoa_Pen_Events.mm
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
//
|
||||||
|
// Definition of macOS Cocoa Pen/Tablet event driver.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// file is missing or damaged, see the license at:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/COPYING.php
|
||||||
|
//
|
||||||
|
// Please see the following page on how to report bugs and issues:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/bugs.php
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#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>
|
||||||
|
|
||||||
|
#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 };
|
||||||
|
|
||||||
|
// The trait list keeps track of traits for every pen ID that appears while
|
||||||
|
// handling events.
|
||||||
|
// AppKit does not tell us what traits are available per pen or tablet, so
|
||||||
|
// we use the first 5 motion events to discover event values that are not
|
||||||
|
// the default value, and enter that knowledge into the traits database.
|
||||||
|
typedef std::map<int, Fl::Pen::Trait> TraitList;
|
||||||
|
static TraitList trait_list_;
|
||||||
|
static int trait_countdown_ { 5 };
|
||||||
|
static int current_pen_id_ { -1 };
|
||||||
|
static Fl::Pen::Trait current_pen_trait_ { Fl::Pen::Trait::DRIVER_AVAILABLE };
|
||||||
|
static Fl::Pen::Trait driver_traits_ {
|
||||||
|
Fl::Pen::Trait::DRIVER_AVAILABLE | Fl::Pen::Trait::PEN_ID |
|
||||||
|
Fl::Pen::Trait::ERASER | Fl::Pen::Trait::PRESSURE |
|
||||||
|
Fl::Pen::Trait::BARREL_PRESSURE | Fl::Pen::Trait::TILT_X |
|
||||||
|
Fl::Pen::Trait::TILT_Y | Fl::Pen::Trait::TWIST
|
||||||
|
// Notably missing: PROXIMITY
|
||||||
|
};
|
||||||
|
|
||||||
|
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 pen_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 {
|
||||||
|
|
||||||
|
// Global mouse position at mouse down event
|
||||||
|
extern int e_x_down;
|
||||||
|
extern int e_y_down;
|
||||||
|
|
||||||
|
namespace Pen {
|
||||||
|
|
||||||
|
// The event data that is made available to the user during event handling
|
||||||
|
struct EventData e;
|
||||||
|
|
||||||
|
} // namespace Pen
|
||||||
|
|
||||||
|
} // namespace Fl
|
||||||
|
|
||||||
|
|
||||||
|
using namespace Fl::Pen;
|
||||||
|
|
||||||
|
|
||||||
|
// Return a bit for everything that AppKit could return.
|
||||||
|
Trait Fl::Pen::driver_traits() {
|
||||||
|
return driver_traits_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Trait Fl::Pen::pen_traits(int pen_id) {
|
||||||
|
auto it = trait_list_.find(pen_id);
|
||||||
|
if (pen_id == 0)
|
||||||
|
return current_pen_trait_;
|
||||||
|
if (it == trait_list_.end()) {
|
||||||
|
return Trait::DRIVER_AVAILABLE;
|
||||||
|
} else {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fl::Pen::subscribe(Fl_Widget* widget) {
|
||||||
|
if (widget == nullptr) return;
|
||||||
|
subscriber_list_.add(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fl::Pen::unsubscribe(Fl_Widget* widget) {
|
||||||
|
if (widget == nullptr) return;
|
||||||
|
subscriber_list_.remove(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Fl::Pen::release() {
|
||||||
|
pushed_ = nullptr;
|
||||||
|
below_pen_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Fl::Pen::event_x() { return e.x; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_y() { return e.y; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_x_root() { return e.rx; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_y_root() { return e.ry; }
|
||||||
|
|
||||||
|
int Fl::Pen::event_pen_id() { return e.pen_id; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_pressure() { return e.pressure; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_barrel_pressure() { return e.barrel_pressure; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_tilt_x() { return e.tilt_x; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_tilt_y() { return e.tilt_y; }
|
||||||
|
|
||||||
|
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() { return e.state; }
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Send an event to all subscribers.
|
||||||
|
*/
|
||||||
|
static int pen_send_all(int event, State trigger) {
|
||||||
|
bool copied = false;
|
||||||
|
// use local value because handler may still change ev values
|
||||||
|
for (auto &it: subscriber_list_) {
|
||||||
|
auto w = it.second->widget();
|
||||||
|
if (w)
|
||||||
|
pen_send(w, event, trigger, copied);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
If a modal window is open, AppKit will send window specific events only there.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
ev.rx = ev.x + eventWindow->x();
|
||||||
|
ev.ry = ev.y + eventWindow->y();
|
||||||
|
if (!is_proximity) {
|
||||||
|
// 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
|
||||||
|
// ev.proximity = [event proximity]; // not supported in AppKit
|
||||||
|
}
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
if (is_proximity) {
|
||||||
|
ev.pen_id = (int)[event vendorID];
|
||||||
|
device_type_ = [event pointingDeviceType];
|
||||||
|
}
|
||||||
|
if (type == NSEventTypeTabletProximity) {
|
||||||
|
if ([event isEnteringProximity]) {
|
||||||
|
// Check if this is the first time we see this pen, or if the pen changed
|
||||||
|
if (current_pen_id_ != ev.pen_id) {
|
||||||
|
current_pen_id_ = ev.pen_id;
|
||||||
|
auto it = trait_list_.find(current_pen_id_);
|
||||||
|
if (it == trait_list_.end()) { // not found, create a new entry
|
||||||
|
trait_list_[current_pen_id_] = Trait::DRIVER_AVAILABLE;
|
||||||
|
trait_countdown_ = 5;
|
||||||
|
pen_send_all(Fl::Pen::DETECTED, State::NONE);
|
||||||
|
// printf("IN RANGE, NEW PEN\n");
|
||||||
|
} else {
|
||||||
|
pen_send_all(Fl::Pen::CHANGED, State::NONE);
|
||||||
|
// printf("IN RANGE, CHANGED PEN\n");
|
||||||
|
}
|
||||||
|
trait_list_[0] = trait_list_[current_pen_id_]; // set current pen traits
|
||||||
|
} else {
|
||||||
|
pen_send_all(Fl::Pen::IN_RANGE, State::NONE);
|
||||||
|
// printf("IN RANGE\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pen_send_all(Fl::Pen::OUT_OF_RANGE, State::NONE);
|
||||||
|
// printf("OUT OF RANGE\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Fl_Widget *receiver = nullptr;
|
||||||
|
bool pushed = false;
|
||||||
|
bool event_data_copied = false;
|
||||||
|
|
||||||
|
if (has_position) {
|
||||||
|
if (trait_countdown_) {
|
||||||
|
trait_countdown_--;
|
||||||
|
if (ev.tilt_x != 0.0) current_pen_trait_ |= Trait::TILT_X;
|
||||||
|
if (ev.tilt_y != 0.0) current_pen_trait_ |= Trait::TILT_Y;
|
||||||
|
if (ev.pressure != 1.0) current_pen_trait_ |= Trait::PRESSURE;
|
||||||
|
if (ev.barrel_pressure != 0.0) current_pen_trait_ |= Trait::BARREL_PRESSURE;
|
||||||
|
if (ev.pen_id != 0) current_pen_trait_ |= Trait::PEN_ID;
|
||||||
|
if (ev.twist != 0.0) current_pen_trait_ |= Trait::TWIST;
|
||||||
|
//if (ev.proximity != 0) current_pen_trait_ |= Trait::PROXIMITY;
|
||||||
|
trait_list_[current_pen_id_] = current_pen_trait_;
|
||||||
|
}
|
||||||
|
fl_xmousewin = eventWindow;
|
||||||
|
if (pushed_ && pushed_->widget() && (Fl::pushed() == pushed_->widget())) {
|
||||||
|
receiver = pushed_->widget();
|
||||||
|
if (Fl::grab() && (Fl::grab() != receiver->top_window()))
|
||||||
|
return 0;
|
||||||
|
if (Fl::modal() && (Fl::modal() != receiver->top_window()))
|
||||||
|
return 0;
|
||||||
|
pushed = true;
|
||||||
|
} else {
|
||||||
|
if (Fl::grab() && (Fl::grab() != eventWindow))
|
||||||
|
return 0;
|
||||||
|
if (Fl::modal() && (Fl::modal() != eventWindow))
|
||||||
|
return 0;
|
||||||
|
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) {
|
||||||
|
State state = (device_type_ == NSPointingDeviceTypeEraser) ? State::ERASER_HOVERS : State::TIP_HOVERS;
|
||||||
|
if (pen_send(bpen_now, Fl::Pen::ENTER, state, 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 {
|
||||||
|
// Anything to do here?
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Fl::e_is_click = 1;
|
||||||
|
Fl::e_x_down = (int)ev.x;
|
||||||
|
Fl::e_y_down = (int)ev.y;
|
||||||
|
if ([event clickCount] > 1)
|
||||||
|
Fl::e_clicks++;
|
||||||
|
else
|
||||||
|
Fl::e_clicks = 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 ( Fl::e_is_click &&
|
||||||
|
( (fabs((int)ev.x - Fl::e_x_down) > 5) ||
|
||||||
|
(fabs((int)ev.y - Fl::e_y_down) > 5) ) )
|
||||||
|
Fl::e_is_click = 0;
|
||||||
|
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;
|
||||||
|
}
|
||||||
73
src/drivers/Stubs/Fl_Stubs_Pen_Events.cxx
Normal file
73
src/drivers/Stubs/Fl_Stubs_Pen_Events.cxx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// Definition of default Pen/Tablet event driver.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// file is missing or damaged, see the license at:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/COPYING.php
|
||||||
|
//
|
||||||
|
// Please see the following page on how to report bugs and issues:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/bugs.php
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#include <FL/platform.H>
|
||||||
|
#include <FL/core/pen_events.H>
|
||||||
|
#include <FL/Fl.H>
|
||||||
|
|
||||||
|
class Fl_Widget;
|
||||||
|
|
||||||
|
namespace Fl {
|
||||||
|
|
||||||
|
namespace Pen {
|
||||||
|
|
||||||
|
// double e_pressure_;
|
||||||
|
|
||||||
|
} // namespace Pen
|
||||||
|
|
||||||
|
} // namespace Fl
|
||||||
|
|
||||||
|
|
||||||
|
using namespace Fl::Pen;
|
||||||
|
|
||||||
|
|
||||||
|
Trait Fl::Pen::driver_traits() { return Trait::NONE; }
|
||||||
|
|
||||||
|
Trait Fl::Pen::pen_traits(int pen_id) { return Trait::NONE; }
|
||||||
|
|
||||||
|
void Fl::Pen::subscribe(Fl_Widget* widget) { }
|
||||||
|
|
||||||
|
void Fl::Pen::unsubscribe(Fl_Widget* widget) { }
|
||||||
|
|
||||||
|
void Fl::Pen::release() { }
|
||||||
|
|
||||||
|
double Fl::Pen::event_x() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_y() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_x_root() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_y_root() { return 0.0; }
|
||||||
|
|
||||||
|
int Fl::Pen::event_pen_id() { return 0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_pressure() { return 1.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_barrel_pressure() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_tilt_x() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_tilt_y() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_twist() { return 0.0; }
|
||||||
|
|
||||||
|
double Fl::Pen::event_proximity() { return 0.0; }
|
||||||
|
|
||||||
|
State Fl::Pen::event_state() { return Fl::Pen::State::NONE; }
|
||||||
|
|
||||||
|
State Fl::Pen::event_trigger() { return Fl::Pen::State::NONE; }
|
||||||
@ -183,6 +183,7 @@ fl_create_example(navigation navigation.cxx fltk::fltk)
|
|||||||
fl_create_example(output output.cxx fltk::fltk)
|
fl_create_example(output output.cxx fltk::fltk)
|
||||||
fl_create_example(overlay overlay.cxx fltk::fltk)
|
fl_create_example(overlay overlay.cxx fltk::fltk)
|
||||||
fl_create_example(pack pack.cxx fltk::fltk)
|
fl_create_example(pack pack.cxx fltk::fltk)
|
||||||
|
fl_create_example(penpal penpal.cxx fltk::fltk)
|
||||||
fl_create_example(pixmap pixmap.cxx fltk::images)
|
fl_create_example(pixmap pixmap.cxx fltk::images)
|
||||||
fl_create_example(pixmap_browser pixmap_browser.cxx fltk::images)
|
fl_create_example(pixmap_browser pixmap_browser.cxx fltk::images)
|
||||||
fl_create_example(preferences preferences.fl fltk::fltk)
|
fl_create_example(preferences preferences.fl fltk::fltk)
|
||||||
|
|||||||
314
test/penpal.cxx
Normal file
314
test/penpal.cxx
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
//
|
||||||
|
// Penpal pen/stylus/tablet test program for the Fast Light Tool Kit (FLTK).
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// file is missing or damaged, see the license at:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/COPYING.php
|
||||||
|
//
|
||||||
|
// Please see the following page on how to report bugs and issues:
|
||||||
|
//
|
||||||
|
// https://www.fltk.org/bugs.php
|
||||||
|
//
|
||||||
|
|
||||||
|
// The Penpal test app is here to test pen/stylus/tablet event distribution
|
||||||
|
// in the Fl::Pen driver. Our main window has three canvases for drawing.
|
||||||
|
// The first canvas is a child of the main window. The second canvas is
|
||||||
|
// inside a group. The third canvas is a subwindow inside the main window.
|
||||||
|
// A second application window is itself yet another canvas.
|
||||||
|
|
||||||
|
// We can test if the events are delivered to the right receiver, if the
|
||||||
|
// mouse and pen offsets are correct. The pen implementation also reacts
|
||||||
|
// to pen pressure and angles. If handle() returns 1 when receiving
|
||||||
|
// Fl::Pen::ENTER, the event handler should not send any mouse events until
|
||||||
|
// Fl::Pen::LEAVE.
|
||||||
|
|
||||||
|
#include <FL/Fl.H>
|
||||||
|
#include <FL/Fl_Window.H>
|
||||||
|
#include <FL/Fl_Box.H>
|
||||||
|
#include <FL/Fl_Menu_Item.H>
|
||||||
|
#include <FL/platform.H>
|
||||||
|
#include <FL/fl_draw.H>
|
||||||
|
#include <FL/fl_message.H>
|
||||||
|
#include <FL/names.h>
|
||||||
|
|
||||||
|
extern Fl_Menu_Item app_menu[];
|
||||||
|
extern int popup_app_menu();
|
||||||
|
Fl_Widget *cv1 { nullptr };
|
||||||
|
Fl_Window *cvwin { nullptr };
|
||||||
|
|
||||||
|
//
|
||||||
|
// The canvas interface implements incremental drawing and handles draw events.
|
||||||
|
// It also implement pressure sensitive drawing with a pen or stylus.
|
||||||
|
// And it implements an overlay plane that visualizes pen event data.
|
||||||
|
//
|
||||||
|
class CanvasInterface {
|
||||||
|
Fl_Widget *widget_ { nullptr };
|
||||||
|
bool in_window_ { false };
|
||||||
|
bool first_draw_ { true };
|
||||||
|
Fl_Offscreen offscreen_ { 0 };
|
||||||
|
Fl_Color color_ { 1 };
|
||||||
|
enum { NONE, HOVER, DRAW, PEN_HOVER, PEN_DRAW } overlay_ { NONE };
|
||||||
|
int ov_x_ { 0 };
|
||||||
|
int ov_y_ { 0 };
|
||||||
|
public:
|
||||||
|
CanvasInterface(Fl_Widget *w) : widget_(w) { }
|
||||||
|
CanvasInterface(Fl_Window *w) : widget_(w), in_window_(true) { }
|
||||||
|
~CanvasInterface() {
|
||||||
|
if (offscreen_) fl_delete_offscreen(offscreen_);
|
||||||
|
}
|
||||||
|
int cv_handle(int event);
|
||||||
|
void cv_draw();
|
||||||
|
void cv_paint();
|
||||||
|
void cv_pen_paint();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Handle mouse and pen events.
|
||||||
|
//
|
||||||
|
int CanvasInterface::cv_handle(int event)
|
||||||
|
{
|
||||||
|
switch (event)
|
||||||
|
{
|
||||||
|
// Event handling for pen events:
|
||||||
|
case Fl::Pen::ENTER: // Return 1 to receive all pen events and suppress mouse events
|
||||||
|
// Pen entered the widget area.
|
||||||
|
color_++;
|
||||||
|
if (color_ > 6) color_ = 1;
|
||||||
|
/* fall through */
|
||||||
|
case Fl::Pen::HOVER:
|
||||||
|
// Pen move over the surface without touching it.
|
||||||
|
overlay_ = PEN_HOVER;
|
||||||
|
ov_x_ = Fl::event_x();
|
||||||
|
ov_y_ = Fl::event_y();
|
||||||
|
widget_->redraw();
|
||||||
|
return 1;
|
||||||
|
case Fl::Pen::TOUCH:
|
||||||
|
// Pen tip or eraser just touched the surface.
|
||||||
|
if (Fl::event_state(FL_CTRL) || Fl::Pen::event_state(Fl::Pen::State::BUTTON0))
|
||||||
|
return popup_app_menu();
|
||||||
|
/* fall through */
|
||||||
|
case Fl::Pen::DRAW:
|
||||||
|
// Pen is dragged over the surface, or hovers with a button pressed.
|
||||||
|
overlay_ = PEN_DRAW;
|
||||||
|
ov_x_ = Fl::event_x();
|
||||||
|
ov_y_ = Fl::event_y();
|
||||||
|
cv_pen_paint();
|
||||||
|
widget_->redraw();
|
||||||
|
return 1;
|
||||||
|
case Fl::Pen::LIFT:
|
||||||
|
// Pen was just lifted from the surface and is now hovering
|
||||||
|
return 1;
|
||||||
|
case Fl::Pen::LEAVE:
|
||||||
|
// The pen left the drawing area.
|
||||||
|
overlay_ = NONE;
|
||||||
|
widget_->redraw();
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Event handling for mouse events:
|
||||||
|
case FL_ENTER:
|
||||||
|
color_++;
|
||||||
|
if (color_ > 6) color_ = 1;
|
||||||
|
/* fall through */
|
||||||
|
case FL_MOVE:
|
||||||
|
overlay_ = HOVER;
|
||||||
|
ov_x_ = Fl::event_x();
|
||||||
|
ov_y_ = Fl::event_y();
|
||||||
|
widget_->redraw();
|
||||||
|
return 1;
|
||||||
|
case FL_PUSH:
|
||||||
|
if (Fl::event_state(FL_CTRL) || Fl::event_button() == FL_RIGHT_MOUSE)
|
||||||
|
return popup_app_menu();
|
||||||
|
/* fall through */
|
||||||
|
case FL_DRAG:
|
||||||
|
overlay_ = DRAW;
|
||||||
|
ov_x_ = Fl::event_x();
|
||||||
|
ov_y_ = Fl::event_y();
|
||||||
|
cv_paint();
|
||||||
|
widget_->redraw();
|
||||||
|
return 1;
|
||||||
|
case FL_RELEASE:
|
||||||
|
return 1;
|
||||||
|
case FL_LEAVE:
|
||||||
|
overlay_ = NONE;
|
||||||
|
widget_->redraw();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Canvas drawing copies the offscreen bitmap and then draws the overlays.
|
||||||
|
//
|
||||||
|
void CanvasInterface::cv_draw()
|
||||||
|
{
|
||||||
|
if (first_draw_) {
|
||||||
|
first_draw_ = false;
|
||||||
|
offscreen_ = fl_create_offscreen(widget_->w(), widget_->h());
|
||||||
|
fl_begin_offscreen(offscreen_);
|
||||||
|
fl_color(FL_WHITE);
|
||||||
|
fl_rectf(0, 0, widget_->w(), widget_->h());
|
||||||
|
fl_end_offscreen();
|
||||||
|
}
|
||||||
|
int dx = in_window_ ? 0 : widget_->x(), dy = in_window_ ? 0 : widget_->y();
|
||||||
|
fl_copy_offscreen(dx, dy, widget_->w(), widget_->h(), offscreen_, 0, 0);
|
||||||
|
|
||||||
|
// Preset values for overlay
|
||||||
|
int r = 10;
|
||||||
|
if (overlay_ == PEN_DRAW)
|
||||||
|
r = static_cast<int>(32.0 * Fl::Pen::event_pressure());
|
||||||
|
fl_color(FL_BLACK);
|
||||||
|
switch (overlay_) {
|
||||||
|
case NONE: break;
|
||||||
|
case PEN_HOVER:
|
||||||
|
fl_color(FL_RED);
|
||||||
|
/* fall through */
|
||||||
|
case HOVER:
|
||||||
|
fl_xyline(ov_x_-10, ov_y_, ov_x_+10);
|
||||||
|
fl_yxline(ov_x_, ov_y_-10, ov_y_+10);
|
||||||
|
break;
|
||||||
|
case PEN_DRAW:
|
||||||
|
fl_color(FL_RED);
|
||||||
|
/* fall through */
|
||||||
|
case DRAW:
|
||||||
|
fl_arc(ov_x_-r, ov_y_-r, 2*r, 2*r, 0, 360);
|
||||||
|
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);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Paint a circle with mouse events.
|
||||||
|
//
|
||||||
|
void CanvasInterface::cv_paint() {
|
||||||
|
if (!offscreen_)
|
||||||
|
return;
|
||||||
|
int dx = in_window_ ? 0 : widget_->x(), dy = in_window_ ? 0 : widget_->y();
|
||||||
|
fl_begin_offscreen(offscreen_);
|
||||||
|
fl_draw_circle(Fl::event_x()-dx-12, Fl::event_y()-dy-12, 24, color_);
|
||||||
|
fl_end_offscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Paint a circle with pen events. If the eraser is touching the surface,
|
||||||
|
// draw a white circle.
|
||||||
|
//
|
||||||
|
void CanvasInterface::cv_pen_paint() {
|
||||||
|
if (!offscreen_)
|
||||||
|
return;
|
||||||
|
int r = static_cast<int>(32.0 * (Fl::Pen::event_pressure()*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, cc);
|
||||||
|
fl_end_offscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// A drawing canvas, based on a minimal widget.
|
||||||
|
//
|
||||||
|
class CanvasWidget : public Fl_Widget, CanvasInterface {
|
||||||
|
public:
|
||||||
|
CanvasWidget(int x, int y, int w, int h, const char *l=nullptr)
|
||||||
|
: Fl_Widget(x, y, w, h, l), CanvasInterface(this) { }
|
||||||
|
~CanvasWidget() override { }
|
||||||
|
int handle(int event) override {
|
||||||
|
auto ret = cv_handle(event);
|
||||||
|
return ret ? ret : Fl_Widget::handle(event);
|
||||||
|
}
|
||||||
|
void draw() override { return cv_draw(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// A drawing canvas based on a window. Can be used as a standalone window
|
||||||
|
// and also as a subwindow inside another window.
|
||||||
|
//
|
||||||
|
class CanvasWindow : public Fl_Window, CanvasInterface {
|
||||||
|
public:
|
||||||
|
CanvasWindow(int x, int y, int w, int h, const char *l=nullptr)
|
||||||
|
: Fl_Window(x, y, w, h, l), CanvasInterface(this) { }
|
||||||
|
~CanvasWindow() override { }
|
||||||
|
int handle(int event) override {
|
||||||
|
auto ret = cv_handle(event);
|
||||||
|
return ret ? ret : Fl_Window::handle(event);
|
||||||
|
}
|
||||||
|
void draw() override { return cv_draw(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A popup menu with a few test tasks.
|
||||||
|
Fl_Menu_Item app_menu[] = {
|
||||||
|
{ "with modal window", 0, [](Fl_Widget*, void*) {
|
||||||
|
fl_message("None of the canvas areas should receive\n"
|
||||||
|
"pen events while this window is open.");
|
||||||
|
} },
|
||||||
|
{ "with non-modal window", 0, [](Fl_Widget*, void*) {
|
||||||
|
auto w = new Fl_Window(400, 32, "Toolbox");
|
||||||
|
w->set_non_modal();
|
||||||
|
w->show();
|
||||||
|
} },
|
||||||
|
{ "unsubscribe middle canvas", 0, [](Fl_Widget*, void*) {
|
||||||
|
if (cv1) Fl::Pen::unsubscribe(cv1);
|
||||||
|
} },
|
||||||
|
{ "resubscribe middle canvas", 0, [](Fl_Widget*, void*) {
|
||||||
|
if (cv1) Fl::Pen::subscribe(cv1);
|
||||||
|
} },
|
||||||
|
{ "delete middle canvas", 0, [](Fl_Widget*, void*) {
|
||||||
|
if (cv1) { cv1->top_window()->redraw(); delete cv1; cv1 = nullptr; }
|
||||||
|
} },
|
||||||
|
{ nullptr }
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Show the menu and run the callback.
|
||||||
|
//
|
||||||
|
int popup_app_menu() {
|
||||||
|
auto mi = app_menu->popup(Fl::event_x(), Fl::event_y(), "Tests");
|
||||||
|
if (mi) mi->do_callback((Fl_Widget*)mi);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Main app entry point
|
||||||
|
//
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// Create our main app window
|
||||||
|
auto window = new Fl_Window(100, 100, 640, 220, "FLTK Pen/Stylus/Tablet test, Ctrl-Tap for menu");
|
||||||
|
|
||||||
|
// One testing canvas is just a regular child widget of the window
|
||||||
|
auto canvas_widget_0 = new CanvasWidget( 10, 10, 200, 200, "CV0");
|
||||||
|
|
||||||
|
// The second canvas is inside a group
|
||||||
|
auto cv1_group = new Fl_Group(215, 5, 210, 210);
|
||||||
|
cv1_group->box(FL_FRAME_BOX);
|
||||||
|
auto canvas_widget_1 = cv1 = new CanvasWidget(220, 10, 200, 200, "CV1");
|
||||||
|
cv1_group->end();
|
||||||
|
|
||||||
|
// The third canvas is a window inside a window, so we can verify
|
||||||
|
// that pen coordinates are calculated correctly.
|
||||||
|
auto canvas_widget_2 = new CanvasWindow(430, 10, 200, 200, "CV2");
|
||||||
|
canvas_widget_2->end();
|
||||||
|
|
||||||
|
window->end();
|
||||||
|
|
||||||
|
// A fourth canvas is a top level window by itself.
|
||||||
|
auto cv_window = cvwin = new CanvasWindow(100, 380, 200, 200, "Canvas Window");
|
||||||
|
|
||||||
|
// All canvases subscribe to pen events.
|
||||||
|
Fl::Pen::subscribe(canvas_widget_0);
|
||||||
|
Fl::Pen::subscribe(canvas_widget_1);
|
||||||
|
Fl::Pen::subscribe(canvas_widget_2);
|
||||||
|
Fl::Pen::subscribe(cv_window);
|
||||||
|
|
||||||
|
window->show(argc, argv);
|
||||||
|
canvas_widget_2->show();
|
||||||
|
cv_window->show();
|
||||||
|
|
||||||
|
return Fl::run();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user