mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
414 lines
12 KiB
Text
414 lines
12 KiB
Text
#include "CocoMM.h"
|
|
|
|
#ifdef GUI_COCOA
|
|
|
|
#define LLOG(x) DLOG(x)
|
|
|
|
namespace Upp {
|
|
|
|
CGImageRef createCGImage(const Image& img)
|
|
{
|
|
if(IsNull(img))
|
|
return NULL;
|
|
Image *km = new Image(img); // to keep data alive
|
|
CGDataProvider *dataProvider = CGDataProviderCreateWithData(km, ~img, img.GetLength() * sizeof(RGBA),
|
|
[](void *info, const void *data, size_t size) { delete (Image *)info; });
|
|
static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // TODO: This is probably wrong...
|
|
Upp::Size isz = img.GetSize();
|
|
CGImageRef cg_img = CGImageCreate(isz.cx, isz.cy, 8, 32, isz.cx * sizeof(RGBA),
|
|
colorSpace, kCGImageAlphaPremultipliedFirst,
|
|
dataProvider, 0, false, kCGRenderingIntentDefault);
|
|
CGDataProviderRelease(dataProvider);
|
|
return cg_img;
|
|
}
|
|
|
|
struct ImageSysData {
|
|
Image img;
|
|
CGImageRef cgimg = NULL;
|
|
|
|
void Init(const Image& img);
|
|
~ImageSysData();
|
|
};
|
|
|
|
// Note: Cocoa U++ has to ignore paintonly flag as the data of image are directly referenced by
|
|
// data provider
|
|
|
|
void ImageSysData::Init(const Image& m)
|
|
{
|
|
img = m;
|
|
cgimg = createCGImage(img);
|
|
// Do not call SysImageRealized(img) here
|
|
}
|
|
|
|
ImageSysData::~ImageSysData()
|
|
{
|
|
CGImageRelease(cgimg);
|
|
}
|
|
|
|
struct ImageSysDataMaker : LRUCache<ImageSysData, int64>::Maker {
|
|
Image img;
|
|
|
|
virtual int64 Key() const { return img.GetSerialId(); }
|
|
virtual int Make(ImageSysData& object) const { object.Init(img); return img.GetLength(); }
|
|
};
|
|
|
|
static LRUCache<ImageSysData, int64> cg_image_cache;
|
|
|
|
static void sCleanImageCache(const Image& img)
|
|
{
|
|
static int Rsz;
|
|
Rsz += img.GetLength();
|
|
if(Rsz > 200 * 200) { // we do not want to do this for each small image painted...
|
|
Rsz = 0;
|
|
cg_image_cache.Remove([](const ImageSysData& object) {
|
|
return object.img.GetRefCount() == 1; // the object.img is nowhere else than in cache
|
|
});
|
|
}
|
|
}
|
|
|
|
void SystemDraw::SysDrawImageOp(int x, int y, const Image& img, Color color)
|
|
{
|
|
GuiLock __;
|
|
|
|
if(img.GetLength() == 0)
|
|
return;
|
|
#if 0
|
|
Image m = IsNull(color) ? img : CachedSetColorKeepAlpha(img, color);
|
|
Image *km = new Image(m); // to keep data alive
|
|
CGDataProvider *dataProvider = CGDataProviderCreateWithData(km, ~m, m.GetLength() * sizeof(RGBA),
|
|
[](void *info, const void *data, size_t size) { delete (Image *)info; });
|
|
static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // TODO: This is probably wrong...
|
|
Upp::Size isz = img.GetSize();
|
|
CGImageRef cgimg = CGImageCreate(isz.cx, isz.cy, 8, 32, isz.cx * sizeof(RGBA),
|
|
colorSpace, kCGImageAlphaPremultipliedFirst,
|
|
dataProvider, 0, false, kCGRenderingIntentDefault);
|
|
|
|
|
|
if(cgimg) {
|
|
Size isz = img.GetSize();
|
|
CGContextSaveGState(cgHandle);
|
|
Point off = GetOffset();
|
|
CGContextTranslateCTM(cgHandle, x + off.x, y + off.y);
|
|
CGContextScaleCTM(cgHandle, 1.0, -1.0);
|
|
CGContextDrawImage(cgHandle, CGRectMake(0, -isz.cy, isz.cx, isz.cy), cgimg);
|
|
CGContextRestoreGState(cgHandle);
|
|
CGImageRelease(cgimg);
|
|
}
|
|
|
|
CGDataProviderRelease(dataProvider);
|
|
|
|
#else
|
|
sCleanImageCache(img);
|
|
|
|
ImageSysDataMaker m;
|
|
m.img = IsNull(color) ? img : CachedSetColorKeepAlpha(img, color); // TODO: Can setcolor be optimized out? By masks e.g.?
|
|
ImageSysData& sd = cg_image_cache.Get(m);
|
|
if(sd.cgimg) {
|
|
Size isz = img.GetSize();
|
|
CGContextSaveGState(cgHandle);
|
|
Point off = GetOffset();
|
|
CGContextTranslateCTM(cgHandle, x + off.x, y + off.y);
|
|
CGContextScaleCTM(cgHandle, 1.0, -1.0);
|
|
CGContextDrawImage(cgHandle, CGRectMake(0, -isz.cy, isz.cx, isz.cy), sd.cgimg);
|
|
CGContextRestoreGState(cgHandle);
|
|
}
|
|
Size sz = Ctrl::GetPrimaryScreenArea().GetSize();
|
|
cg_image_cache.Shrink(4 * sz.cx * sz.cy, 1000); // Cache must be after Paint because of PaintOnly!
|
|
#endif
|
|
}
|
|
|
|
// TODO: https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview
|
|
|
|
enum {
|
|
MM_Arrow = 1,
|
|
MM_Wait,
|
|
MM_IBeam,
|
|
MM_No,
|
|
MM_SizeAll,
|
|
MM_SizeHorz,
|
|
MM_SizeVert,
|
|
MM_SizeTopLeft,
|
|
MM_SizeTop,
|
|
MM_SizeTopRight,
|
|
MM_SizeLeft,
|
|
MM_SizeRight,
|
|
MM_SizeBottomLeft,
|
|
MM_SizeBottom,
|
|
MM_SizeBottomRight,
|
|
MM_Cross,
|
|
MM_Hand,
|
|
};
|
|
|
|
|
|
#define FCURSOR_(x) { static Image h; ONCELOCK { h = CreateImage(Size(1, 1), Black); h.SetAuxData(x); } return h; }
|
|
|
|
Image Image::Arrow() FCURSOR_(MM_Arrow)
|
|
Image Image::Wait() FCURSOR_(MM_Wait)
|
|
Image Image::IBeam() FCURSOR_(MM_IBeam)
|
|
Image Image::No() FCURSOR_(MM_No)
|
|
Image Image::SizeAll() FCURSOR_(MM_SizeAll)
|
|
Image Image::SizeHorz() FCURSOR_(MM_SizeHorz)
|
|
Image Image::SizeVert() FCURSOR_(MM_SizeVert)
|
|
Image Image::SizeTopLeft() FCURSOR_(MM_SizeTopLeft)
|
|
Image Image::SizeTop() FCURSOR_(MM_SizeTop)
|
|
Image Image::SizeTopRight() FCURSOR_(MM_SizeTopRight)
|
|
Image Image::SizeLeft() FCURSOR_(MM_SizeLeft)
|
|
Image Image::SizeRight() FCURSOR_(MM_SizeRight)
|
|
Image Image::SizeBottomLeft() FCURSOR_(MM_SizeBottomLeft)
|
|
Image Image::SizeBottom() FCURSOR_(MM_SizeBottom)
|
|
Image Image::SizeBottomRight() FCURSOR_(MM_SizeBottomRight)
|
|
Image Image::Cross() FCURSOR_(MM_Cross)
|
|
Image Image::Hand() FCURSOR_(MM_Hand)
|
|
|
|
// TODO: Missing kinds (sizers mostly)
|
|
// https://stackoverflow.com/questions/10733228/native-osx-lion-resize-cursor-for-custom-nswindow-or-nsview/21786835#21786835
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
|
|
|
#define XCURSOR(id) \
|
|
if([NSCursor respondsToSelector:@selector(id)]) return [NSCursor performSelector:@selector(id)];
|
|
|
|
NSCursor *GetNSCursor(int kind)
|
|
{
|
|
switch(kind) {
|
|
case MM_SizeAll:
|
|
XCURSOR(_moveCursor);
|
|
break;
|
|
case MM_SizeHorz:
|
|
XCURSOR(_windowResizeEastWestCursor);
|
|
break;
|
|
case MM_SizeVert:
|
|
XCURSOR(_windowResizeNorthSouthCursor);
|
|
break;
|
|
case MM_SizeTopLeft:
|
|
XCURSOR(_windowResizeNorthWestSouthEastCursor);
|
|
break;
|
|
case MM_SizeTop:
|
|
XCURSOR(_windowResizeNorthCursor);
|
|
break;
|
|
case MM_SizeTopRight:
|
|
XCURSOR(_windowResizeNorthEastSouthWestCursor);
|
|
break;
|
|
case MM_SizeLeft:
|
|
XCURSOR(_windowResizeWestCursor);
|
|
break;
|
|
case MM_SizeRight:
|
|
XCURSOR(_windowResizeEastCursor);
|
|
break;
|
|
case MM_SizeBottomLeft:
|
|
XCURSOR(_windowResizeNorthEastSouthWestCursor);
|
|
break;
|
|
case MM_SizeBottom:
|
|
XCURSOR(_windowResizeSouthCursor);
|
|
break;
|
|
case MM_SizeBottomRight:
|
|
XCURSOR(_windowResizeNorthWestSouthEastCursor);
|
|
break;
|
|
case MM_IBeam: return [NSCursor IBeamCursor];
|
|
case MM_Cross: return [NSCursor crosshairCursor];
|
|
case MM_Hand: return [NSCursor pointingHandCursor];
|
|
case MM_No: return [NSCursor operationNotAllowedCursor];
|
|
}
|
|
return [NSCursor arrowCursor];
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
NSImage *CreateNSImage(const Image& img, CGImageRef cgimg)
|
|
{
|
|
sCleanImageCache(img);
|
|
Size isz = img.GetSize();
|
|
double scale = 1.0 / DPI(1);
|
|
NSSize size;
|
|
size.width = scale * isz.cx;
|
|
size.height = scale * isz.cy;
|
|
NSImage *nsimage = [[NSImage alloc] initWithCGImage:cgimg size:size];
|
|
cg_image_cache.Shrink(4 * 1024 * 768, 1000); // Cache must be after Paint because of PaintOnly!
|
|
return nsimage;
|
|
}
|
|
|
|
struct NSImageSysData {
|
|
Image img;
|
|
NSImage *nsimage = NULL;
|
|
|
|
void Init(const Image& img);
|
|
~NSImageSysData();
|
|
};
|
|
|
|
void NSImageSysData::Init(const Image& img)
|
|
{
|
|
ImageSysDataMaker m;
|
|
m.img = img;
|
|
nsimage = CreateNSImage(img, cg_image_cache.Get(m).cgimg);
|
|
}
|
|
|
|
NSImageSysData::~NSImageSysData()
|
|
{
|
|
if(nsimage)
|
|
[nsimage release];
|
|
}
|
|
|
|
struct NSImageSysDataMaker : LRUCache<NSImageSysData, int64>::Maker {
|
|
Image img;
|
|
|
|
virtual int64 Key() const { return img.GetSerialId(); }
|
|
virtual int Make(NSImageSysData& object) const { object.Init(img); return img.GetLength(); }
|
|
};
|
|
|
|
static LRUCache<NSImageSysData, int64> nsimage_cache;
|
|
|
|
NSImage *GetNSImage(const Image& img)
|
|
{
|
|
NSImageSysDataMaker m;
|
|
m.img = img;
|
|
NSImage *nsimage = nsimage_cache.Get(m).nsimage;
|
|
nsimage_cache.Shrink(4 * 1024*768, 1000);
|
|
return nsimage;
|
|
}
|
|
|
|
void Ctrl::SetNSAppImage(const Image& img)
|
|
{
|
|
ImageSysDataMaker m;
|
|
m.img = img;
|
|
ImageSysData& sd = cg_image_cache.Get(m);
|
|
static CGImageRef cgimg;
|
|
static NSImage *nsimg;
|
|
if(sd.cgimg != cgimg) {
|
|
cgimg = sd.cgimg;
|
|
if(nsimg)
|
|
[nsimg release];
|
|
nsimg = CreateNSImage(img, cgimg);
|
|
}
|
|
[NSApp setApplicationIconImage:nsimg];
|
|
}
|
|
|
|
void Ctrl::SetMouseCursor(const Image& img)
|
|
{
|
|
if(GetDragAndDropSource())
|
|
return;
|
|
int64 h = img.GetAuxData();
|
|
if(h) {
|
|
[GetNSCursor(h) set];
|
|
return;
|
|
}
|
|
ImageSysDataMaker m;
|
|
m.img = img;
|
|
ImageSysData& sd = cg_image_cache.Get(m);
|
|
static CGImageRef cgimg;
|
|
static NSCursor *cursor;
|
|
if(sd.cgimg != cgimg) {
|
|
cgimg = sd.cgimg;
|
|
if(cursor)
|
|
[cursor release];
|
|
NSImage *nsimg = CreateNSImage(img, cgimg);
|
|
double scale = 1.0 / DPI(1);
|
|
Point p = img.GetHotSpot();
|
|
NSPoint hot;
|
|
hot.x = scale * p.x;
|
|
hot.y = scale * p.y;
|
|
cursor = [[NSCursor alloc] initWithImage:nsimg hotSpot:hot];
|
|
[nsimg release];
|
|
}
|
|
[cursor set];
|
|
}
|
|
|
|
void ImageDraw::Init(int cx, int cy)
|
|
{
|
|
ib.Create(cx, cy);
|
|
Fill(~ib, RGBAZero(), ib.GetLength());
|
|
|
|
static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
|
|
SystemDraw::Init(CGBitmapContextCreateWithData(~ib, cx, cy, 8, cx * sizeof(RGBA),
|
|
colorSpace, kCGImageAlphaPremultipliedFirst,
|
|
NULL, NULL), NULL);
|
|
CGContextTranslateCTM(cgHandle, 0, cy);
|
|
|
|
if(Ctrl::GetDisplayScale() == 2) { // scale can only be 1 or 2...
|
|
CGContextScaleCTM(cgHandle, 2, -2);
|
|
CGContextTranslateCTM(cgHandle, 0, -cy / 2.0);
|
|
}
|
|
else
|
|
CGContextScaleCTM(cgHandle, 1, -1);
|
|
}
|
|
|
|
ImageDraw::ImageDraw(Size sz)
|
|
{
|
|
Init(sz.cx, sz.cy);
|
|
}
|
|
|
|
ImageDraw::ImageDraw(int cx, int cy)
|
|
{
|
|
Init(cx, cy);
|
|
}
|
|
|
|
Draw& ImageDraw::Alpha()
|
|
{
|
|
if(!alpha)
|
|
alpha.Create(ib.GetSize());
|
|
return *alpha;
|
|
}
|
|
|
|
ImageDraw::~ImageDraw()
|
|
{
|
|
CGContextRelease(cgHandle);
|
|
handle = NULL; // avoid releasing invalid handle in ~SystemDraw
|
|
}
|
|
|
|
Image ImageDraw::GetStraight() const
|
|
{
|
|
ImageBuffer ib2(ib.GetSize());
|
|
memcpy_t(~ib2, ~ib, ib.GetLength());
|
|
if(alpha) {
|
|
RGBA *e = ib2.End();
|
|
RGBA *t = ib2;
|
|
const RGBA *s = alpha->ib;
|
|
while(t < e) {
|
|
t->a = s->r;
|
|
s++;
|
|
t++;
|
|
}
|
|
}
|
|
return Image(ib2);
|
|
}
|
|
|
|
ImageDraw::operator Image() const
|
|
{
|
|
if(alpha)
|
|
return Premultiply(GetStraight());
|
|
ImageBuffer ib2(ib.GetSize());
|
|
memcpy_t(~ib2, ~ib, ib.GetLength());
|
|
return Image(ib2);
|
|
}
|
|
|
|
Image GetIconForFile(const char *path)
|
|
{
|
|
ImageDraw iw(DPI(16, 16));
|
|
|
|
CGContextRef cg = (CGContextRef) iw.GetCGHandle();
|
|
|
|
|
|
NSImage *image;
|
|
CFRef<CFStringRef> fexe = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
|
|
image = [[NSWorkspace sharedWorkspace]iconForFile:(__bridge NSString *)~fexe];
|
|
/*
|
|
}
|
|
else { // not used any more
|
|
CFRef<CFStringRef> fext = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
|
|
image = *ext == '*' ? [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)]
|
|
: [[NSWorkspace sharedWorkspace]iconForFileType:(__bridge NSString *)~fext];
|
|
}
|
|
*/
|
|
NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES];
|
|
NSGraphicsContext* cgc = [NSGraphicsContext currentContext];
|
|
[NSGraphicsContext setCurrentContext:gc];
|
|
[image drawInRect:NSMakeRect(0, 0, DPI(16), DPI(16))];
|
|
[NSGraphicsContext setCurrentContext:cgc];
|
|
|
|
return iw;
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|