Add pen driver refinements.
* macOS driver is now releasable * API is stabelized
This commit is contained in:
parent
65d36c7910
commit
25e8db15f8
@ -44,12 +44,12 @@ namespace Pen {
|
||||
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.
|
||||
By returning 1 to pen events, the user can ask for more detailed events.
|
||||
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 form the event handler indicates that the event was not handled.
|
||||
FLTK may convert the tablet event into a mouse event and resend it to
|
||||
the relevant widget.
|
||||
Returning 0 Fl::Pen::ENTER tells FLTK to supress further pen events until
|
||||
Fl::Pen::LEAVE, and convert them into mouse events instead.
|
||||
|
||||
@{
|
||||
*/
|
||||
@ -59,6 +59,8 @@ namespace Pen {
|
||||
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
|
||||
@ -67,7 +69,7 @@ enum class Trait : uint32_t {
|
||||
/// 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
|
||||
DEVICE_ID = 0x0008,
|
||||
PEN_ID = 0x0008,
|
||||
/// Pen may have an eraser tip
|
||||
ERASER = 0x0010,
|
||||
/// Pen returns a pressure value
|
||||
@ -143,7 +145,7 @@ enum class State : uint32_t {
|
||||
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
|
||||
@ -153,7 +155,7 @@ 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
|
||||
@ -163,7 +165,7 @@ 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
|
||||
@ -182,37 +184,47 @@ inline State& operator|=(State& lhs, State rhs) {
|
||||
*/
|
||||
enum Event {
|
||||
/**
|
||||
A pen was detected for the first time for this subscriber.
|
||||
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.
|
||||
|
||||
Return 1 to receive ENTER and LEAVE events.
|
||||
*/
|
||||
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.
|
||||
Return 1 to receive MOVE events.
|
||||
*/
|
||||
ENTER,
|
||||
|
||||
/**
|
||||
\brief Pen left the widget area.
|
||||
No event values are set.
|
||||
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.
|
||||
Return 1 to receive DRAW events.
|
||||
event_trigger() returns TIP_DOWN or ERASER_DOWN.
|
||||
*/
|
||||
TOUCH,
|
||||
|
||||
@ -222,10 +234,10 @@ enum Event {
|
||||
*/
|
||||
LIFT,
|
||||
|
||||
/** Pen moved without touching the surface. */
|
||||
/** Pen moved without touching the surface and no button is pressed. */
|
||||
HOVER,
|
||||
|
||||
/** Pen moved while touching the surface. */
|
||||
/** Pen moved while touching the surface, or any button is pressed. */
|
||||
DRAW,
|
||||
|
||||
/**
|
||||
@ -258,17 +270,38 @@ enum Event {
|
||||
*/
|
||||
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] window must be a mapped (shown, visible) window
|
||||
\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(Fl_Window *window);
|
||||
FL_EXPORT extern Trait pen_traits(int pen_id = 0);
|
||||
|
||||
/**
|
||||
\brief Receive pen events when the pen is inside this widget, or all events.
|
||||
\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
|
||||
@ -276,7 +309,7 @@ FL_EXPORT extern Trait pen_traits(Fl_Window *window);
|
||||
FL_EXPORT extern void subscribe(Fl_Widget* widget);
|
||||
|
||||
/**
|
||||
\brief Stop receiving pen events for this widget.
|
||||
\brief Stop receiving Pen::ENTER for this widget.
|
||||
Deleting a widget will automatically unsubscribe it.
|
||||
\param widget Widget to unsubscribe from pen events
|
||||
*/
|
||||
@ -291,7 +324,7 @@ FL_EXPORT extern void unsubscribe(Fl_Widget* widget);
|
||||
FL_EXPORT extern void grab(Fl_Widget* widget);
|
||||
|
||||
/**
|
||||
* \brief Release pen event handling after grab().
|
||||
\brief Release pen event handling after grab().
|
||||
*/
|
||||
FL_EXPORT extern void release();
|
||||
|
||||
@ -323,9 +356,9 @@ 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::DEVICE_ID
|
||||
\see Trait::PEN_ID
|
||||
*/
|
||||
FL_EXPORT extern int event_id();
|
||||
FL_EXPORT extern int event_pen_id();
|
||||
|
||||
/**
|
||||
\brief Returns the pressure between the tip or eraser and the surface.
|
||||
@ -360,39 +393,38 @@ FL_EXPORT extern double 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
|
||||
\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
|
||||
\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)
|
||||
\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.
|
||||
* \return true if any bit is set
|
||||
\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
|
||||
\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();
|
||||
|
||||
@ -429,7 +461,6 @@ FL_EXPORT extern State event_trigger();
|
||||
SDL3:
|
||||
https://github.com/libsdl-org/SDL/blob/main/include/SDL3/SDL_pen.h
|
||||
https://wiki.libsdl.org/SDL3/CategoryPen
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@ -91,6 +91,24 @@ 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 };
|
||||
@ -101,7 +119,7 @@ struct EventData {
|
||||
double pressure { 1.0 };
|
||||
double barrel_pressure { 0.0 };
|
||||
double twist { 0.0 };
|
||||
int device_id { 0 };
|
||||
int pen_id { 0 };
|
||||
Fl::Pen::State state { (Fl::Pen::State)0 };
|
||||
Fl::Pen::State trigger { (Fl::Pen::State)0 };
|
||||
};
|
||||
@ -125,14 +143,20 @@ struct EventData e;
|
||||
using namespace Fl::Pen;
|
||||
|
||||
|
||||
// TODO: implement this
|
||||
// Return a bit for everything that AppKit could return.
|
||||
Trait Fl::Pen::driver_traits() {
|
||||
return Trait::DRIVER_AVAILABLE /* | and more! */ ;
|
||||
return driver_traits_;
|
||||
}
|
||||
|
||||
// TODO: implement this
|
||||
Trait Fl::Pen::pen_traits(Fl_Window *window) {
|
||||
return Trait::DRIVER_AVAILABLE /* | and more as we discover them! */ ;
|
||||
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) {
|
||||
@ -157,7 +181,7 @@ double Fl::Pen::event_x_root() { return e.rx; }
|
||||
|
||||
double Fl::Pen::event_y_root() { return e.ry; }
|
||||
|
||||
int Fl::Pen::event_id() { return e.device_id; }
|
||||
int Fl::Pen::event_pen_id() { return e.pen_id; }
|
||||
|
||||
double Fl::Pen::event_pressure() { return e.pressure; }
|
||||
|
||||
@ -257,6 +281,19 @@ static int pen_send(Fl_Widget *w, int event, State trigger, bool &copied) {
|
||||
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,
|
||||
*/
|
||||
@ -281,7 +318,6 @@ static State button_to_trigger(NSInteger button, bool down)
|
||||
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)
|
||||
{
|
||||
@ -325,11 +361,7 @@ bool fl_cocoa_tablet_handler(NSEvent *event, Fl_Window *eventWindow)
|
||||
// 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 {
|
||||
if (!is_proximity) {
|
||||
// Get the pressure data.
|
||||
ev.pressure = [event pressure];
|
||||
ev.barrel_pressure = [event tangentialPressure];
|
||||
@ -339,6 +371,7 @@ bool fl_cocoa_tablet_handler(NSEvent *event, Fl_Window *eventWindow)
|
||||
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)
|
||||
@ -357,12 +390,52 @@ bool fl_cocoa_tablet_handler(NSEvent *event, Fl_Window *eventWindow)
|
||||
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();
|
||||
@ -381,7 +454,8 @@ bool fl_cocoa_tablet_handler(NSEvent *event, Fl_Window *eventWindow)
|
||||
}
|
||||
below_pen_ = nullptr;
|
||||
if (bpen_now) {
|
||||
if (pen_send(bpen_now, Fl::Pen::ENTER, State::NONE, event_data_copied)) {
|
||||
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);
|
||||
}
|
||||
@ -393,7 +467,7 @@ bool fl_cocoa_tablet_handler(NSEvent *event, Fl_Window *eventWindow)
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// TODO: handle non-position events (proximity)
|
||||
// Anything to do here?
|
||||
}
|
||||
|
||||
if (!receiver)
|
||||
|
||||
@ -36,9 +36,9 @@ namespace Pen {
|
||||
using namespace Fl::Pen;
|
||||
|
||||
|
||||
Trait Fl::Pen::driver_traits() { return static_cast<Trait>(0); }
|
||||
Trait Fl::Pen::driver_traits() { return Trait::NONE; }
|
||||
|
||||
Trait Fl::Pen::pen_traits(Fl_Window *window) { return static_cast<Trait>(0); }
|
||||
Trait Fl::Pen::pen_traits(int pen_id) { return Trait::NONE; }
|
||||
|
||||
void Fl::Pen::subscribe(Fl_Widget* widget) { }
|
||||
|
||||
@ -56,7 +56,7 @@ double Fl::Pen::event_x_root() { return 0.0; }
|
||||
|
||||
double Fl::Pen::event_y_root() { return 0.0; }
|
||||
|
||||
int Fl::Pen::event_id() { return 0; }
|
||||
int Fl::Pen::event_pen_id() { return 0; }
|
||||
|
||||
double Fl::Pen::event_pressure() { return 1.0; }
|
||||
|
||||
|
||||
@ -33,6 +33,11 @@
|
||||
#include <FL/fl_draw.H>
|
||||
#include <FL/names.h>
|
||||
|
||||
//
|
||||
// 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 };
|
||||
@ -54,31 +59,45 @@ public:
|
||||
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:
|
||||
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: /* fall through */
|
||||
case Fl::Pen::TOUCH:
|
||||
// Pen tip or eraser just touched the surface.
|
||||
/* 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: return 1;
|
||||
case Fl::Pen::LEAVE: overlay_ = NONE; 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:
|
||||
@ -91,7 +110,8 @@ int CanvasInterface::cv_handle(int event)
|
||||
ov_y_ = Fl::event_y();
|
||||
widget_->redraw();
|
||||
return 1;
|
||||
case FL_PUSH: /* fall through */
|
||||
case FL_PUSH:
|
||||
/* fall through */
|
||||
case FL_DRAG:
|
||||
overlay_ = DRAW;
|
||||
ov_x_ = Fl::event_x();
|
||||
@ -99,13 +119,21 @@ int CanvasInterface::cv_handle(int event)
|
||||
cv_paint();
|
||||
widget_->redraw();
|
||||
return 1;
|
||||
case FL_RELEASE: return 1;
|
||||
case FL_LEAVE: overlay_ = NONE; widget_->redraw(); return 1;
|
||||
case FL_RELEASE:
|
||||
return 1;
|
||||
case FL_LEAVE:
|
||||
overlay_ = NONE;
|
||||
widget_->redraw();
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CanvasInterface::cv_draw() {
|
||||
//
|
||||
// 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());
|
||||
@ -138,11 +166,14 @@ void CanvasInterface::cv_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);
|
||||
printf("%d\n", Fl::Pen::event_id());
|
||||
// printf("%d\n", Fl::Pen::event_pen_id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Paint a circle with mouse events.
|
||||
//
|
||||
void CanvasInterface::cv_paint() {
|
||||
if (!offscreen_)
|
||||
return;
|
||||
@ -152,6 +183,10 @@ void CanvasInterface::cv_paint() {
|
||||
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;
|
||||
@ -164,6 +199,9 @@ void CanvasInterface::cv_pen_paint() {
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 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)
|
||||
@ -176,6 +214,10 @@ public:
|
||||
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)
|
||||
@ -188,23 +230,34 @@ public:
|
||||
void draw() override { return cv_draw(); }
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Main app entry point
|
||||
//
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Create our main app window
|
||||
auto window = new Fl_Window(100, 100, 640, 220);
|
||||
|
||||
// One testing canvas is just a regular child widget of the window
|
||||
auto canvas_widget_0 = new CanvasWidget( 10, 10, 200, 200, "CV1");
|
||||
|
||||
// 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 = new CanvasWidget(220, 10, 200, 200, "CV2");
|
||||
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, "CV3");
|
||||
canvas_widget_2->end();
|
||||
|
||||
window->end();
|
||||
|
||||
// A fourth canvas is a top level window by itself.
|
||||
auto cv_window = new CanvasWindow(100, 380, 200, 200, "CV 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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user