summaryrefslogtreecommitdiff
path: root/src/Fl_Cairo.cxx
blob: 5f9a18ee4ea75609562edabecc2e42284d09af8d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
//
// Special Cairo support for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-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
//

// This file implements the FLTK Cairo Window support (since 1.3.x):
//
// - cmake -D FLTK_OPTION_CAIRO_WINDOW  or
// - cmake -D FLTK_OPTION_CAIRO_EXT
//
// Preprocessor macro FLTK_HAVE_CAIRO is defined for both options.
// Preprocessor macro FLTK_HAVE_CAIRO_EXT is defined only for "CAIRO_EXT".
// Both macros are defined in 'FL/fl_config.h'.

#include "Fl_Private.H" // includes <FL/fl_config.h>

#ifdef FLTK_HAVE_CAIRO

// Define USE_MAC_OS for convenience (below). We use macOS specific features
// if USE_MAC_OS is defined, otherwise we're using X11 (XQuartz) on macOS

#if defined __APPLE__ && !defined(FLTK_USE_X11)
#define USE_MAC_OS
#include <ApplicationServices/ApplicationServices.h>
#endif

#include <FL/platform.H>
#include <FL/Fl_Window.H>

// Cairo is currently supported for the following platforms:
// Windows, macOS (Apple Quartz), X11, Wayland

#if defined(_WIN32)               // Windows
#  include <cairo-win32.h>
#elif defined(FLTK_USE_WAYLAND)   // Wayland or hybrid
#  include "../src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H"
#  include "../src/drivers/Wayland/Fl_Wayland_Window_Driver.H"
#  if defined(FLTK_USE_X11)
#    include <cairo-xlib.h>
#  else
     static void *fl_gc = NULL;
#  endif
#elif defined(FLTK_USE_X11)       // X11
#  include <cairo-xlib.h>
#elif defined(__APPLE__)          // macOS
#  include <cairo-quartz.h>
#else
#  error Cairo is not supported on this platform.
#endif

// static initialization

Fl_Cairo_State Fl::Private::cairo_state_; ///< current Cairo context information

/** When FLTK_HAVE_CAIRO is defined and cairo_autolink_context() is true,
  any current window dc is linked to a current Cairo context.
  This is not the default, because it may not be necessary
  to add Cairo support to all fltk supported windows.
  When you wish to associate a Cairo context in this mode,
  you need to call explicitly in your draw() overridden method,
  Fl::cairo_make_current(Fl_Window*). This will create a Cairo context
  only for this Window.
  Still in custom Cairo application it is possible to handle
  completely this process automatically by setting \p alink to true.
  In this last case, you don't need anymore to call Fl::cairo_make_current().
  You can use Fl::cairo_cc() to get the current Cairo context anytime.

  \note Only available if built with CMake option FLTK_OPTION_CAIRO_WINDOW=ON.
*/
void Fl::cairo_autolink_context(bool alink) {
  Private::cairo_state_.autolink(alink);
}

/**
  Gets the current autolink mode for Cairo support.
  \retval false if no Cairo context autolink is made for each window.
  \retval true if any fltk window is attached a Cairo context when it
  is current. \see void cairo_autolink_context(bool alink)

  \note Only available if built with CMake option FLTK_OPTION_CAIRO_EXT=ON.
  */
bool Fl::cairo_autolink_context() {
  return Private::cairo_state_.autolink();
}

/** Gets the current Cairo context linked with a fltk window. */
cairo_t *Fl::cairo_cc() {
  return Private::cairo_state_.cc();
}

/** Sets the current Cairo context to \p c.
  Set \p own to true if you want fltk to handle this cc deletion.

  \note Only available if built with CMake option FLTK_OPTION_CAIRO_WINDOW=ON.
*/
void Fl::cairo_cc(cairo_t *c, bool own /* = false */) {
  Private::cairo_state_.cc(c, own);
}


// Fl_Cairo_State

void Fl_Cairo_State::autolink(bool b) {
#ifdef FLTK_HAVE_CAIROEXT
  autolink_ = b;
#else
  Fl::fatal("In Fl::autolink(bool): Cairo autolink() feature is only "
            "available with CMake FLTK_OPTION_CAIRO_EXT.\n"
            "Quitting now.");
#endif
}

/**
  Provides a Cairo context for window \a wi.

  This is needed in a draw() override if Fl::cairo_autolink_context()
  returns false, which is the default.
  The cairo_context() does not need to be freed as it is freed every time
  a new Cairo context is created. When the program terminates,
  a call to Fl::cairo_make_current(0) will destroy any residual context.

  \note A new Cairo context is not always re-created when this method
    is used. In particular, if the current graphical context and the current
    window didn't change between two calls, the previous gc is internally kept,
    thus optimizing the drawing performances.
    Also, after this call, Fl::cairo_cc() is adequately updated with this
    Cairo context.

  \note Only available when CMake option '-D FLTK_OPTION_CAIRO_WINDOW' is set.

  \return The valid cairo_t *cairo context associated to this window.
  \retval NULL if \a wi is NULL or maybe with GL windows under Wayland
*/
cairo_t *Fl::cairo_make_current(Fl_Window *wi) {
  if (!wi)
    return NULL;
  cairo_t *cairo_ctxt;

#if defined(FLTK_USE_WAYLAND)
  if (fl_wl_display()) { // true means using wayland backend
    struct wld_window *xid = fl_wl_xid(wi);
    if (!xid || !xid->buffer)
      return NULL; // this may happen with GL windows or if window is not shown
    cairo_ctxt = xid->buffer->draw_buffer.cairo_;
    Fl::Private::cairo_state_.cc(cairo_ctxt, false);
    return cairo_ctxt;
  }
#endif

  if (fl_gc == 0) {  // means remove current cc
    Fl::cairo_cc(0); // destroy any previous cc
    Fl::Private::cairo_state_.window(0);
    return 0;
  }

  // don't re-create a context if it's the same gc/window combination
  if (fl_gc == Fl::Private::cairo_state_.gc() && fl_xid(wi) == (Window)Fl::Private::cairo_state_.window())
    return Fl::cairo_cc();

  // Scale the Cairo context appropriately. This is platform dependent

#if !defined(USE_MAC_OS)
  float scale = Fl::screen_scale(wi->screen_num()); // get the screen scaling factor
#endif

#if defined(FLTK_USE_X11)
  cairo_ctxt = Fl::Private::cairo_make_current(0, wi->w() * scale, wi->h() * scale);
#else
  // on macOS, scaling is done before by Fl_Window::make_current(), on Windows, the size is not used
  cairo_ctxt = Fl::Private::cairo_make_current(fl_gc, wi->w(), wi->h());
#endif

  Fl::Private::cairo_state_.window((void *)fl_xid(wi));

#if !defined(USE_MAC_OS)
  cairo_scale(cairo_ctxt, scale, scale);
#endif
  return cairo_ctxt;
}

/*
  Creates transparently a cairo_surface_t object.
  gc is an HDC context in Windows, a CGContext* in Quartz, and
  a display on X11 (not used on this platform)
*/

static cairo_surface_t *cairo_create_surface(void *gc, int W, int H) {
#if defined(FLTK_USE_X11)
  return cairo_xlib_surface_create(fl_display, fl_window, fl_visual->visual, W, H);
#elif defined(FLTK_USE_WAYLAND)
  return NULL;
#elif defined(_WIN32)
  return cairo_win32_surface_create((HDC)gc);
#elif defined(__APPLE__)
  return cairo_quartz_surface_create_for_cg_context((CGContextRef)gc, W, H);
#else
#error Cairo is not supported on this platform.
#endif
}

/**
  Creates a Cairo context from a \a gc only, gets its window size or
  offscreen size if fl_window is null.

  \note Only available if CMake FLTK_OPTION_CAIRO_WINDOW is enabled.
*/
cairo_t *Fl::Private::cairo_make_current(void *gc) {
  int W = 0, H = 0;
#if defined(FLTK_USE_X11) || defined(FLTK_USE_WAYLAND)
  // FIXME X11 get W,H
  // gc will be the window handle here
  // # warning FIXME get W,H for cairo_make_current(void*)
#elif defined(__APPLE__)
  if (fl_window) {
    W = Fl_Window::current()->w();
    H = Fl_Window::current()->h();
  } else {
    W = CGBitmapContextGetWidth(fl_gc);
    H = CGBitmapContextGetHeight(fl_gc);
  }
#elif defined(_WIN32)
  // we don't need any W,H for Windows
#else
#error Cairo is not supported on this platform.
#endif

  if (!gc) {
    Fl::cairo_cc(0);
    cairo_state_.gc(0); // keep track for next time
    return 0;
  }
  if (gc == Fl::Private::cairo_state_.gc() &&
      fl_window == (Window)Fl::Private::cairo_state_.window() &&
      cairo_state_.cc() != 0)
    return Fl::cairo_cc();
  cairo_state_.gc(fl_gc); // keep track for next time
  cairo_surface_t *s = cairo_create_surface(gc, W, H);
  cairo_t *c = cairo_create(s);
  cairo_surface_destroy(s);
  cairo_state_.cc(c);
  return c;
}

/**
  Creates a Cairo context from a \p gc and the given size.

  \note Only available if CMake FLTK_OPTION_CAIRO_WINDOW is enabled.
*/
cairo_t *Fl::Private::cairo_make_current(void *gc, int W, int H) {
  if (gc == Fl::Private::cairo_state_.gc() &&
      fl_window == (Window)Fl::Private::cairo_state_.window() &&
      cairo_state_.cc() != 0) // no need to create a cc, just return that one
    return cairo_state_.cc();

  // we need to (re-)create a fresh cc ...
  cairo_state_.gc(gc); // keep track for next time
  cairo_surface_t *s = cairo_create_surface(gc, W, H);

#if defined(USE_MAC_OS) && defined(FLTK_HAVE_CAIROEXT)
  CGAffineTransform at = CGContextGetCTM((CGContextRef)gc);
  CGContextSaveGState((CGContextRef)gc);
  CGContextConcatCTM((CGContextRef)gc, CGAffineTransformInvert(at));
#endif

  cairo_t *c = cairo_create(s);

#if defined(USE_MAC_OS) && defined(FLTK_HAVE_CAIROEXT)
  CGContextRestoreGState((CGContextRef)gc);
#endif

  cairo_state_.cc(c); //  and purge any previously owned context
  cairo_surface_destroy(s);
  return c;
}

/** Flush Cairo drawings on Cairo context \p c.
  This is \b required on Windows if you use the Cairo context provided
  by the "Cairo autolink" option. Call this when all your drawings on
  the Cairo context are finished. This is maybe not necessary on other
  platforms than Windows but it does no harm if you call it always.

  You don't need to use this if you use an Fl_Cairo_Window which does
  this automatically after the draw callback returns.

  Code example for "Cairo autolink" mode:

  In the overridden draw() method of your subclass of Fl_Window or any
  widget:
  \code
    cairo_t *cc = Fl::cairo_cc();   // get the "autolink" Cairo context
    // ... your Cairo drawings are here ...
    Fl::cairo_flush(cc);            // flush Cairo drawings to the device
  \endcode

  If you configure FLTK with CMake option
  \c 'FLTK_OPTION_CAIRO_WINDOW' (i.e. without CMake option
  \c 'FLTK_OPTION_CAIRO_EXT') or if you don't enable the \c 'autolink' Cairo
  context you may do the equivalent to use Cairo drawings in an
  overridden draw() method of derived classes by using
  \code
    // get the  Cairo context for the \c window
    cairo_t *cc = Fl::cairo_make_current(window);
    // ... your Cairo drawings are here ...
    Fl::cairo_flush(cc); // flush Cairo drawings to the device
  \endcode
  \see Fl::cairo_autolink_context(bool)
  \see Fl::cairo_make_current(Fl_Window*);
*/
FL_EXPORT extern void Fl::cairo_flush(cairo_t *c) {
  // flush Cairo drawings: necessary at least for Windows
  cairo_surface_t *s = cairo_get_target(c);
  cairo_surface_flush(s);
}

// Silence compiler warning if none of the Cairo options has been selected

#else

FL_EXPORT int fltk_cairo_dummy() {
  return 1;
}

#endif // FLTK_HAVE_CAIRO