summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorManolo Gouy <Manolo>2014-08-27 11:55:57 +0000
committerManolo Gouy <Manolo>2014-08-27 11:55:57 +0000
commit8997131377a02004503c40ba2a96e6937bfd39c1 (patch)
tree666c4f7d681f1a47f4f3d8372b20d6fc96d2b582 /src
parent22beee52afd4e06e0f7fa2e19ce86e132b75730b (diff)
Added the Fl_Shaped_Window class to support windows of arbitrary shapes.
The new class is fully Doxygen-documented. Added an example program (example/shapedwindow.cxx) that exercises the new class. Modified all IDE-supporting files accordingly. git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@10255 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/Fl_Shaped_Window.cxx369
-rw-r--r--src/Fl_cocoa.mm7
-rw-r--r--src/Fl_x.cxx6
-rw-r--r--src/Makefile1
5 files changed, 382 insertions, 2 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index afacb823c..4b8983d46 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -55,6 +55,7 @@ set(CPPFILES
Fl_Round_Button.cxx
Fl_Scroll.cxx
Fl_Scrollbar.cxx
+ Fl_Shaped_Window.cxx
Fl_Shared_Image.cxx
Fl_Single_Window.cxx
Fl_Slider.cxx
diff --git a/src/Fl_Shaped_Window.cxx b/src/Fl_Shaped_Window.cxx
new file mode 100644
index 000000000..e874c7098
--- /dev/null
+++ b/src/Fl_Shaped_Window.cxx
@@ -0,0 +1,369 @@
+//
+// "$Id"
+//
+// Fl_Shaped_Window source file for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 2010-2014 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:
+//
+// http://www.fltk.org/COPYING.php
+//
+// Please report all bugs and problems to:
+//
+// http://www.fltk.org/str.php
+//
+
+#include <FL/x.H>
+#include <FL/Fl_Shaped_Window.H>
+#include <FL/Fl_Bitmap.H>
+#include <FL/Fl_Pixmap.H>
+
+#ifdef WIN32
+# include <malloc.h> // needed for VisualC2010
+#elif !defined(__APPLE__)
+#include <dlfcn.h>
+#define ShapeBounding 0
+#define ShapeSet 0
+#endif
+
+/** Create a shaped window with the given size and title */
+Fl_Shaped_Window::Fl_Shaped_Window(int w, int h, const char* title)
+ : Fl_Window(w, h, title), lw_(0), lh_(0), shape_(0), todelete_(0) {
+ type(FL_SHAPED_WINDOW);
+ border(false);
+#if defined(__APPLE__)
+ mask = NULL;
+#endif
+ }
+
+/** Create a shaped window with the given position, size and title */
+Fl_Shaped_Window::Fl_Shaped_Window(int x, int y, int w, int h, const char* title)
+ : Fl_Window(x, y, w, h, title), lw_(0), lh_(0), shape_(0), todelete_(0) {
+ type(FL_SHAPED_WINDOW);
+ border(false);
+#if defined(__APPLE__)
+ mask = NULL;
+#endif
+ }
+
+/** Destroys the shaped window but not its associated Fl_Image */
+Fl_Shaped_Window::~Fl_Shaped_Window() {
+ if (todelete_) delete todelete_;
+#if defined(__APPLE__)
+ if (mask) {
+ CGImageRelease(mask);
+ }
+#endif
+}
+
+
+#if defined(__APPLE__)
+
+static void MyProviderReleaseData (void *info, const void *data, size_t size) {
+ delete[] (uchar*)data;
+}
+
+// bitwise inversion of all 4-bit quantities
+static const unsigned char swapped[16] = {0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15};
+
+static inline uchar swap_byte(const uchar b) {
+ // reverse the order of bits of byte b: 1->8 becomes 8->1
+ return (swapped[b & 0xF] << 4) | swapped[b >> 4];
+}
+
+void Fl_Shaped_Window::draw() {
+# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
+ if (mask && (CGContextClipToMask != NULL)) CGContextClipToMask(fl_gc, CGRectMake(0,0,w(),h()), mask); // requires Mac OS 10.4
+ CGContextSaveGState(fl_gc);
+# endif
+ Fl_Window::draw();
+# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
+ CGContextRestoreGState(fl_gc);
+# endif
+ }
+
+#elif defined(WIN32)
+
+static inline BYTE bit(int x) { return (BYTE)(1 << (x%8)); }
+
+static HRGN bitmap2region(Fl_Bitmap* image) {
+ HRGN hRgn = 0;
+ /* Does this need to be dynamically determined, perhaps? */
+ const int ALLOC_UNIT = 100;
+ DWORD maxRects = ALLOC_UNIT;
+
+ RGNDATA* pData = (RGNDATA*)malloc(sizeof(RGNDATAHEADER)+(sizeof(RECT)*maxRects));
+ pData->rdh.dwSize = sizeof(RGNDATAHEADER);
+ pData->rdh.iType = RDH_RECTANGLES;
+ pData->rdh.nCount = pData->rdh.nRgnSize = 0;
+ SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
+
+ const int bytesPerLine = (image->w() + 7)/8;
+ BYTE* p, *data = (BYTE*)image->array;
+ for (int y = 0; y < image->h(); y++) {
+ // each row, left to right
+ for (int x = 0; x < image->w(); x++) {
+ int x0 = x;
+ while (x < image->w()) {
+ p = data + x / 8;
+ if (!((*p) & bit(x))) break; // transparent pixel
+ x++;
+ }
+ if (x > x0) {
+ RECT *pr;
+ /* Add the pixels (x0, y) to (x, y+1) as a new rectangle
+ * in the region
+ */
+ if (pData->rdh.nCount >= maxRects) {
+ maxRects += ALLOC_UNIT;
+ pData = (RGNDATA*)realloc(pData, sizeof(RGNDATAHEADER)
+ + (sizeof(RECT)*maxRects));
+ }
+ pr = (RECT*)&pData->Buffer;
+ SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1);
+ if (x0 < pData->rdh.rcBound.left)
+ pData->rdh.rcBound.left = x0;
+ if (y < pData->rdh.rcBound.top)
+ pData->rdh.rcBound.top = y;
+ if (x > pData->rdh.rcBound.right)
+ pData->rdh.rcBound.right = x;
+ if (y+1 > pData->rdh.rcBound.bottom)
+ pData->rdh.rcBound.bottom = y+1;
+ pData->rdh.nCount++;
+ /* On Windows98, ExtCreateRegion() may fail if the
+ * number of rectangles is too large (ie: >
+ * 4000). Therefore, we have to create the region by
+ * multiple steps.
+ */
+ if (pData->rdh.nCount == 2000) {
+ HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER)
+ + (sizeof(RECT)*maxRects), pData);
+ if (hRgn) {
+ CombineRgn(hRgn, hRgn, h, RGN_OR);
+ DeleteObject(h);
+ } else
+ hRgn = h;
+ pData->rdh.nCount = 0;
+ SetRect(&pData->rdh.rcBound, MAXLONG, MAXLONG, 0, 0);
+ }
+ }
+ }
+ /* Go to next row */
+ data += bytesPerLine;
+ }
+ /* Create or extend the region with the remaining rectangles*/
+ HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER)
+ + (sizeof(RECT)*maxRects), pData);
+ if (hRgn) {
+ CombineRgn(hRgn, hRgn, h, RGN_OR);
+ DeleteObject(h);
+ } else hRgn = h;
+ free(pData); // I've created the region so I can free this now, right?
+ return hRgn;
+}
+
+void Fl_Shaped_Window::draw() {
+ if ((lw_ != w() || lh_ != h()) && shape_) {
+ // size of window has changed since last time
+ lw_ = w();
+ lh_ = h();
+ Fl_Bitmap* temp = (Fl_Bitmap*)shape_->copy(lw_, lh_);
+ HRGN region = bitmap2region(temp);
+ SetWindowRgn(fl_xid(this), region, TRUE); // the system deletes the region when it's no longer needed
+ delete temp;
+ }
+ Fl_Window::draw();
+}
+
+#else
+
+
+#ifndef FL_DOXYGEN
+void Fl_Shaped_Window::combine_mask()
+{
+ typedef void (*XShapeCombineMask_type)(Display*, int, int, int, int, Pixmap, int);
+ static XShapeCombineMask_type XShapeCombineMask_f = NULL;
+ static int beenhere = 0;
+ typedef Bool (*XShapeQueryExtension_type)(Display*, int*, int*);
+ int error_base, shapeEventBase;
+ if (!beenhere) {
+ beenhere = 1;
+ fl_open_display();
+ void *handle = dlopen(NULL, RTLD_LAZY); // search symbols in executable
+ XShapeQueryExtension_type XShapeQueryExtension_f = (XShapeQueryExtension_type)dlsym(handle, "XShapeQueryExtension");
+ XShapeCombineMask_f = (XShapeCombineMask_type)dlsym(handle, "XShapeCombineMask");
+ // make sure that the X server has the SHAPE extension
+ if ( !( XShapeQueryExtension_f && XShapeCombineMask_f &&
+ XShapeQueryExtension_f(fl_display, &shapeEventBase, &error_base) ) ) XShapeCombineMask_f = NULL;
+ }
+ if (!XShapeCombineMask_f) return;
+ lw_ = w();
+ lh_ = h();
+ Fl_Bitmap* temp = (Fl_Bitmap*)shape_->copy(lw_, lh_);
+ Pixmap pbitmap = XCreateBitmapFromData(fl_display, fl_xid(this),
+ (const char*)temp->array,
+ temp->w(), temp->h());
+ XShapeCombineMask_f(fl_display, fl_xid(this), ShapeBounding, 0, 0, pbitmap, ShapeSet);
+ if (pbitmap != None) XFreePixmap(fl_display, pbitmap);
+ delete temp;
+}
+#endif // !FL_DOXYGEN
+
+void Fl_Shaped_Window::draw() {
+ if (( lw_ != w() || lh_ != h() ) && shape_) {
+ // size of window has changed since last time
+ combine_mask();
+ }
+ Fl_Window::draw();
+}
+
+#endif // __APPLE__
+
+
+void Fl_Shaped_Window::shape_bitmap_(Fl_Bitmap* b) {
+ if (todelete_) { delete todelete_; todelete_ = NULL; }
+ shape_ = b;
+ lw_ = lh_ = 0; // so change in mask is detected
+#if defined(__APPLE__)
+ if (mask) {
+ CGImageRelease(mask);
+ mask = NULL;
+ }
+ if (b) {
+ // complement mask bits and perform bitwise inversion of all bytes and also reverse top and bottom
+ int bytes_per_row = (b->w() + 7)/8;
+ uchar *from = new uchar[bytes_per_row * b->h()];
+ for (int i = 0; i < b->h(); i++) {
+ uchar *p = (uchar*)b->array + bytes_per_row * i;
+ uchar *last = p + bytes_per_row;
+ uchar *q = from + (b->h() - 1 - i) * bytes_per_row;
+ while (p < last) {
+ *q++ = swap_byte(~*p++);
+ }
+ }
+ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, from, bytes_per_row * b->h(), MyProviderReleaseData);
+ mask = CGImageMaskCreate(b->w(), b->h(), 1, 1, bytes_per_row, provider, NULL, false);
+ CFRelease(provider);
+ }
+#endif
+}
+
+
+#if defined(__APPLE__) // on the mac, use an 8-bit mask
+/* the image can be of any depth
+ offset gives the byte offset from the pixel start to the byte used to construct the shape
+ */
+void Fl_Shaped_Window::shape_alpha_(Fl_RGB_Image* img, int offset) {
+ int i, d = img->d(), w = img->w(), h = img->h();
+ if (todelete_) { delete todelete_; todelete_ = NULL; }
+ shape_ = img;
+ lw_ = lh_ = 0; // so change in mask is detected
+ if (mask) {
+ CGImageRelease(mask);
+ mask = NULL;
+ }
+ if (shape_) {
+ // reverse top and bottom and convert to gray scale if img->d() == 3 and complement bits
+ int bytes_per_row = w * d;
+ uchar *from = new uchar[w * h];
+ for ( i = 0; i < h; i++) {
+ uchar *p = (uchar*)img->array + bytes_per_row * i + offset;
+ uchar *last = p + bytes_per_row;
+ uchar *q = from + (h - 1 - i) * w;
+ while (p < last) {
+ if (d == 3) {
+ unsigned u = *p++;
+ u += *p++;
+ u += *p++;
+ *q++ = ~(u/3);
+ }
+ else {
+ *q++ = ~(*p);
+ p += d;
+ }
+ }
+ }
+ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, from, w * h, MyProviderReleaseData);
+ mask = CGImageMaskCreate(w, h, 8, 8, w, provider, NULL, false);
+ CFRelease(provider);
+ }
+}
+
+#else
+
+/* the img image can be of any depth
+ offset gives the byte offset from the pixel start to the byte used to construct the shape
+ */
+void Fl_Shaped_Window::shape_alpha_(Fl_RGB_Image* img, int offset) {
+ int i, j, d = img->d(), w = img->w(), h = img->h(), bytesperrow = (w+7)/8;
+ unsigned u;
+ uchar byte, onebit;
+ // build an Fl_Bitmap covering the non-fully transparent/black part of the image
+ const uchar* bits = new uchar[h*bytesperrow]; // to store the bitmap
+ const uchar* alpha = img->array + offset; // points to alpha value of rgba pixels
+ for (i = 0; i < h; i++) {
+ uchar *p = (uchar*)bits + i * bytesperrow;
+ byte = 0;
+ onebit = 1;
+ for (j = 0; j < w; j++) {
+ if (d == 3) {
+ u = *alpha;
+ u += *(alpha+1);
+ u += *(alpha+2);
+ }
+ else u = *alpha;
+ if (u > 0) { // if the pixel is not fully transparent/black
+ byte |= onebit; // turn on the corresponding bit of the bitmap
+ }
+ onebit = onebit << 1; // move the single set bit one position to the left
+ if (onebit == 0 || j == w-1) {
+ onebit = 1;
+ *p++ = byte; // store in bitmap one pack of bits
+ byte = 0;
+ }
+ alpha += d; // point to alpha value of next pixel
+ }
+ }
+ Fl_Bitmap* bitmap = new Fl_Bitmap(bits, w, h);
+ bitmap->alloc_array = 1;
+ shape_bitmap_(bitmap);
+ todelete_ = bitmap;
+}
+
+#endif
+
+
+void Fl_Shaped_Window::shape_pixmap_(Fl_Pixmap* pixmap) {
+ Fl_RGB_Image* rgba = new Fl_RGB_Image(pixmap);
+ shape_alpha_(rgba, 3);
+ delete rgba;
+}
+
+/** Set the window's shape with an image.
+ The \p img argument can be an Fl_Bitmap, Fl_Pixmap or Fl_RGB_Image.
+ \li With Fl_Bitmap or Fl_Pixmap, the shaped window covers the image part where bitmap bits equal one,
+ or where the pixmap is not fully transparent.
+ \li With an Fl_RGB_Image with an alpha channel (depths 2 or 4), the shaped window covers the image part
+ that is not fully transparent.
+ \li With an Fl_RGB_Image of depth 1 (gray-scale) or 3 (RGB), the shaped window covers the non-black image part.
+
+ On some platforms, an 8-bit shape-mask is used when \p img is an Fl_RGB_Image:
+ with depths 2 or 4, the image alpha channel becomes the shape mask such that areas with alpha = 0
+ are out of the shaped window;
+ with depths 1 or 3, white and black are in and out of the
+ shaped window, respectively, and other colors give intermediate masking scores.
+ */
+void Fl_Shaped_Window::shape(const Fl_Image* img) {
+ int d = img->d();
+ if (d && img->count() >= 2) shape_pixmap_((Fl_Pixmap*)img);
+ else if (d == 0) shape_bitmap_((Fl_Bitmap*)img);
+ else if (d == 2 || d == 4) shape_alpha_((Fl_RGB_Image*)img, d - 1);
+ else if ((d == 1 || d == 3) && img->count() == 1) shape_alpha_((Fl_RGB_Image*)img, 0);
+}
+
+//
+// End of "$Id".
+//
diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm
index 5de916139..68b21dc43 100644
--- a/src/Fl_cocoa.mm
+++ b/src/Fl_cocoa.mm
@@ -2361,6 +2361,7 @@ void Fl_X::flush()
fl_x_to_redraw = NULL;
}
+//bool Fl_X::make_shaped = false;
/*
* go ahead, create that (sub)window
@@ -2517,6 +2518,10 @@ void Fl_X::make(Fl_Window* w)
[cw setFrameOrigin:crect.origin];
[cw setHasShadow:YES];
[cw setAcceptsMouseMovedEvents:YES];
+ if (w->type() == FL_SHAPED_WINDOW) {
+ [cw setOpaque:NO]; // shaped windows must be non opaque
+ [cw setBackgroundColor:[NSColor clearColor]]; // and with transparent background color
+ }
x->xid = cw;
x->w = w; w->i = x;
x->wait_for_expose = 1;
@@ -2645,7 +2650,7 @@ void Fl_Window::show() {
labeltype(FL_NO_LABEL);
}
Fl_Tooltip::exit(this);
- if (!shown() || !i) {
+ if (!shown()) {
Fl_X::make(this);
} else {
if ( !parent() ) {
diff --git a/src/Fl_x.cxx b/src/Fl_x.cxx
index e071b8d83..b3045c8a6 100644
--- a/src/Fl_x.cxx
+++ b/src/Fl_x.cxx
@@ -35,6 +35,7 @@
# include <FL/fl_draw.H>
# include <FL/Fl_Paged_Device.H>
# include <FL/Fl_Shared_Image.H>
+# include <FL/Fl_Shaped_Window.H>
# include <FL/fl_ask.H>
# include <FL/filename.H>
# include <stdio.h>
@@ -2511,6 +2512,9 @@ void Fl_X::make_xid(Fl_Window* win, XVisualInfo *visual, Colormap colormap)
}
#endif
+ if (win->type() == FL_SHAPED_WINDOW) {
+ ((Fl_Shaped_Window*)win)->combine_mask();
+ }
XMapWindow(fl_display, xp->xid);
if (showit) {
win->set_visible();
@@ -2836,7 +2840,7 @@ void Fl_Window::show() {
if (!shown()) {
fl_open_display();
// Don't set background pixel for double-buffered windows...
- if (type() == FL_WINDOW && can_boxcheat(box())) {
+ if (type() != FL_DOUBLE_WINDOW && can_boxcheat(box())) {
fl_background_pixel = int(fl_xpixel(color()));
}
Fl_X::make_xid(this);
diff --git a/src/Makefile b/src/Makefile
index 625e067a7..1376c3595 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -71,6 +71,7 @@ CPPFILES = \
Fl_Round_Button.cxx \
Fl_Scroll.cxx \
Fl_Scrollbar.cxx \
+ Fl_Shaped_Window.cxx \
Fl_Shared_Image.cxx \
Fl_Single_Window.cxx \
Fl_Slider.cxx \