Set up a custom NSScrollView clone to see if that's more or less feasible than hijacking NSScrollView itself. Doesn't do scrolling yet.

This commit is contained in:
Pietro Gagliardi 2015-09-10 14:27:45 -04:00
parent 568e9f65e8
commit 51e60359ab
5 changed files with 711 additions and 0 deletions

12
macarea/alt/area.h Normal file
View File

@ -0,0 +1,12 @@
// 8 september 2015
#import <Cocoa/Cocoa.h>
#import <stdint.h>
#import "ui.h"
extern uiArea *newArea(uiAreaHandler *ah);
extern uiDrawContext *newContext(CGContextRef);
extern NSView *areaGetView(uiArea *);
extern void areaUpdateScroll(uiArea *);

242
macarea/alt/area.m Normal file
View File

@ -0,0 +1,242 @@
// 9 september 2015
#include "area.h"
// TODO remove this
void addConstraint(NSView *view, NSString *constraint, NSDictionary *metrics, NSDictionary *views)
{
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:constraint
options:0
metrics:metrics
views:views];
[view addConstraints:constraints];
}
// NSScrollers have no intrinsic size; here we give it one
@interface areaScroller : NSScroller {
BOOL libui_vertical;
}
- (id)initWithFrame:(NSRect)r vertical:(BOOL)v;
@end
@implementation areaScroller
- (id)initWithFrame:(NSRect)r vertical:(BOOL)v
{
self = [super initWithFrame:r];
if (self)
self->libui_vertical = v;
return self;
}
- (NSSize)intrinsicContentSize
{
NSSize s;
CGFloat scrollerWidth;
s = [super intrinsicContentSize];
scrollerWidth = [NSScroller scrollerWidthForControlSize:[self controlSize]
scrollerStyle:[self scrollerStyle]];
if (self->libui_vertical)
s.width = scrollerWidth;
else
s.height = scrollerWidth;
return s;
}
- (void)setControlSize:(NSControlSize)size
{
[super setControlSize:size];
[self invalidateIntrinsicContentSize];
}
- (void)setScrollerStyle:(NSScrollerStyle)style
{
[super setScrollerStyle:style];
[self invalidateIntrinsicContentSize];
}
@end
@interface areaDrawingView : NSView {
uiArea *libui_a;
}
- (id)initWithFrame:(NSRect)r area:(uiArea *)a;
@end
@interface areaView : NSView {
uiArea *libui_a;
areaDrawingView *drawingView;
areaScroller *hscrollbar;
areaScroller *vscrollbar;
}
- (id)initWithFrame:(NSRect)r area:(uiArea *)a;
@end
struct uiArea {
// uiDarwinControl c;
areaView *view;
uiAreaHandler *ah;
};
@implementation areaDrawingView
- (id)initWithFrame:(NSRect)r area:(uiArea *)a
{
self = [super initWithFrame:r];
if (self)
self->libui_a = a;
return self;
}
- (void)drawRect:(NSRect)r
{
CGContextRef c;
uiAreaDrawParams dp;
c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
dp.Context = newContext(c);
dp.ClientWidth = [self frame].size.width;
dp.ClientHeight = [self frame].size.height;
dp.ClipX = r.origin.x;
dp.ClipY = r.origin.y;
dp.ClipWidth = r.size.width;
dp.ClipHeight = r.size.height;
// TODO DPI
// TODO scroll position
(*(self->libui_a->ah->Draw))(self->libui_a->ah, self->libui_a, &dp);
}
- (BOOL)isFlipped
{
return YES;
}
@end
@implementation areaView
- (id)initWithFrame:(NSRect)r area:(uiArea *)a
{
NSScrollerStyle style;
CGFloat swidth;
NSMutableDictionary *views;
NSLayoutConstraint *constraint;
self = [super initWithFrame:r];
if (self) {
self->libui_a = a;
self->drawingView = [[areaDrawingView alloc] initWithFrame:NSZeroRect area:self->libui_a];
[self->drawingView setTranslatesAutoresizingMaskIntoConstraints:NO];
style = [NSScroller preferredScrollerStyle];
swidth = [NSScroller scrollerWidthForControlSize:NSRegularControlSize
scrollerStyle:style];
self->hscrollbar = [[areaScroller alloc]
initWithFrame:NSMakeRect(0, 0, swidth * 5, swidth)
vertical:NO];
[self->hscrollbar setScrollerStyle:style];
[self->hscrollbar setKnobStyle:NSScrollerKnobStyleDefault];
[self->hscrollbar setControlTint:NSDefaultControlTint];
[self->hscrollbar setControlSize:NSRegularControlSize];
//TODO [self->hscrollbar setArrowsPosition:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx];
[self->hscrollbar setTranslatesAutoresizingMaskIntoConstraints:NO];
self->vscrollbar = [[areaScroller alloc]
initWithFrame:NSMakeRect(0, 0, swidth, swidth * 5)
vertical:YES];
[self->vscrollbar setScrollerStyle:style];
[self->vscrollbar setKnobStyle:NSScrollerKnobStyleDefault];
[self->vscrollbar setControlTint:NSDefaultControlTint];
[self->vscrollbar setControlSize:NSRegularControlSize];
//TODO [self->vscrollbar setArrowsPosition:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx];
[self->vscrollbar setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self->drawingView];
[self addSubview:self->hscrollbar];
[self addSubview:self->vscrollbar];
// use visual constraints to arrange:
// - the drawing view and vertical scrollbar horizontally
// - the drawing view and horizontal scrollbar vertically
// - the horizontal scrollbar flush left
// - the vertical scrollbar flush top
views = [NSMutableDictionary new];
[views setObject:self->drawingView forKey:@"drawingView"];
[views setObject:self->hscrollbar forKey:@"hscrollbar"];
[views setObject:self->vscrollbar forKey:@"vscrollbar"];
addConstraint(self, @"H:|[drawingView][vscrollbar]|", nil, views);
addConstraint(self, @"V:|[drawingView][hscrollbar]|", nil, views);
addConstraint(self, @"H:|[hscrollbar]", nil, views);
addConstraint(self, @"V:|[vscrollbar]", nil, views);
[views release];
// use explicit layout constraints to line up
// - the bottom edge of the drawing view with the bottom edge of the vertical scrollbar
// - the right edge of the drawing view with the right edge of the horizontal scrollbar
constraint = [NSLayoutConstraint constraintWithItem:self->drawingView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self->vscrollbar
attribute:NSLayoutAttributeBottom
multiplier:1
constant:0];
[self addConstraint:constraint];
[constraint release];
constraint = [NSLayoutConstraint constraintWithItem:self->drawingView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self->hscrollbar
attribute:NSLayoutAttributeRight
multiplier:1
constant:0];
[self addConstraint:constraint];
[constraint release];
}
return self;
}
@end
uiArea *newArea(uiAreaHandler *ah)
{
uiArea *a;
// TODO
a = (uiArea *) malloc(sizeof (uiArea));
a->ah = ah;
a->view = [[areaView alloc] initWithFrame:NSZeroRect area:a];
// set initial state
// TODO do this on other platforms?
areaUpdateScroll(a);
return a;
}
NSView *areaGetView(uiArea *a)
{
return a->view;
}
void areaUpdateScroll(uiArea *a)
{
/* TODO
NSRect frame;
frame.origin = NSMakePoint(0, 0);
frame.size.width = (*(a->ah->HScrollMax))(a->ah, a);
frame.size.height = (*(a->ah->VScrollMax))(a->ah, a);
[a->documentView setFrame:frame];
*/
}

148
macarea/alt/draw.m Normal file
View File

@ -0,0 +1,148 @@
// 6 september 2015
#include "area.h"
// TODO some pixel thick lines aren't actually pixel thick
struct uiDrawContext {
CGContextRef c;
BOOL useRGBA;
CGFloat r;
CGFloat g;
CGFloat b;
CGFloat a;
};
uiDrawContext *newContext(CGContextRef ctxt)
{
uiDrawContext *c;
// TODO use uiNew
c = (uiDrawContext *) malloc(sizeof (uiDrawContext));
c->c = ctxt;
return c;
}
void uiDrawBeginPathRGB(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b)
{
c->useRGBA = YES;
c->r = ((CGFloat) r) / 255;
c->g = ((CGFloat) g) / 255;
c->b = ((CGFloat) b) / 255;
c->a = 1.0;
CGContextBeginPath(c->c);
}
void uiDrawBeginPathRGBA(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
c->useRGBA = YES;
c->r = ((CGFloat) r) / 255;
c->g = ((CGFloat) g) / 255;
c->b = ((CGFloat) b) / 255;
c->a = ((CGFloat) a) / 255;
CGContextBeginPath(c->c);
}
// TODO 0.25 for retina? some say yes, some say no
// TODO same adjustment for cairo
#define topoint(x) (((CGFloat) x) + 0.5)
void uiDrawMoveTo(uiDrawContext *c, intmax_t x, intmax_t y)
{
CGContextMoveToPoint(c->c, topoint(x), topoint(y));
}
void uiDrawLineTo(uiDrawContext *c, intmax_t x, intmax_t y)
{
CGContextAddLineToPoint(c->c, topoint(x), topoint(y));
}
// TODO width-1/height-1? (also for cairo)
void uiDrawRectangle(uiDrawContext *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height)
{
CGContextAddRect(c->c, CGRectMake(topoint(x), topoint(y), width, height));
}
void uiDrawArcTo(uiDrawContext *c, intmax_t xCenter, intmax_t yCenter, intmax_t radius, double startAngle, double endAngle, int lineFromCurrentPointToStart)
{
if (!lineFromCurrentPointToStart) {
// see http://stackoverflow.com/questions/31489157/extra-line-when-drawing-an-arc-in-swift
// TODO verify correctness
CGFloat x, y;
x = topoint(xCenter);
y = topoint(yCenter);
x += radius * cos(startAngle);
y -= radius * sin(startAngle);
CGContextMoveToPoint(c->c, x, y);
}
CGContextAddArc(c->c,
topoint(xCenter), topoint(yCenter),
radius,
startAngle, endAngle,
0);
}
void uiDrawBezierTo(uiDrawContext *c, intmax_t c1x, intmax_t c1y, intmax_t c2x, intmax_t c2y, intmax_t endX, intmax_t endY)
{
CGContextAddCurveToPoint(c->c,
topoint(c1x), topoint(c1y),
topoint(c2x), topoint(c2y),
topoint(endX), topoint(endY));
}
void uiDrawCloseFigure(uiDrawContext *c)
{
CGContextClosePath(c->c);
}
void uiDrawStroke(uiDrawContext *c, uiDrawStrokeParams *p)
{
switch (p->Cap) {
case uiDrawLineCapFlat:
CGContextSetLineCap(c->c, kCGLineCapButt);
break;
case uiDrawLineCapRound:
CGContextSetLineCap(c->c, kCGLineCapRound);
break;
case uiDrawLineCapSquare:
CGContextSetLineCap(c->c, kCGLineCapSquare);
break;
}
switch (p->Join) {
case uiDrawLineJoinMiter:
CGContextSetLineJoin(c->c, kCGLineJoinMiter);
CGContextSetMiterLimit(c->c, p->MiterLimit);
break;
case uiDrawLineJoinRound:
CGContextSetLineJoin(c->c, kCGLineJoinRound);
break;
case uiDrawLineJoinBevel:
CGContextSetLineJoin(c->c, kCGLineJoinBevel);
break;
}
CGContextSetLineWidth(c->c, p->Thickness);
if (c->useRGBA)
CGContextSetRGBStrokeColor(c->c, c->r, c->g, c->b, c->a);
else {
// TODO
}
CGContextStrokePath(c->c);
}
void uiDrawFill(uiDrawContext *c, uiDrawFillMode mode)
{
if (c->useRGBA)
CGContextSetRGBFillColor(c->c, c->r, c->g, c->b, c->a);
else {
// TODO
}
switch (mode) {
case uiDrawFillModeWinding:
CGContextFillPath(c->c);
break;
case uiDrawFillModeAlternate:
CGContextEOFillPath(c->c);
break;
}
}

171
macarea/alt/main.m Normal file
View File

@ -0,0 +1,171 @@
// 4 september 2015
#define _GNU_SOURCE
#include "area.h"
#include <math.h>
// #qo LDFLAGS: -framework Foundation -framework AppKit -framework CoreGraphics
struct handler {
uiAreaHandler ah;
};
static uiArea *area;
static struct handler h;
//static NSTextField *nhspinb;
//static NSTextField *nvspinb;
static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)
{
uiDrawStrokeParams sp;
uiDrawBeginPathRGB(p->Context, 0xFF, 0x00, 0x00);
uiDrawMoveTo(p->Context, p->ClipX + 5, p->ClipY + 5);
uiDrawLineTo(p->Context, (p->ClipX + p->ClipWidth) - 5, (p->ClipY + p->ClipHeight) - 5);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGB(p->Context, 0x00, 0x00, 0xC0);
uiDrawMoveTo(p->Context, p->ClipX, p->ClipY);
uiDrawLineTo(p->Context, p->ClipX + p->ClipWidth, p->ClipY);
uiDrawLineTo(p->Context, 50, 150);
uiDrawLineTo(p->Context, 50, 50);
uiDrawCloseFigure(p->Context);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinRound;
sp.Thickness = 5;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGBA(p->Context, 0x00, 0xC0, 0x00, 0x80);
uiDrawRectangle(p->Context, 120, 80, 50, 50);
uiDrawFill(p->Context, uiDrawFillModeWinding);
uiDrawBeginPathRGB(p->Context, 0x00, 0x80, 0x00);
uiDrawMoveTo(p->Context, 5, 10);
uiDrawLineTo(p->Context, 5, 50);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGB(p->Context, 0x80, 0xC0, 0x00);
uiDrawMoveTo(p->Context, 400, 100);
uiDrawArcTo(p->Context,
400, 100,
50,
30. * (M_PI / 180.),
// note the end angle here
// in GDI, the second angle to AngleArc() is relative to the start, not to 0
330. * (M_PI / 180.),
1);
// TODO add a checkbox for this
uiDrawLineTo(p->Context, 400, 100);
uiDrawArcTo(p->Context,
510, 100,
50,
30. * (M_PI / 180.),
330. * (M_PI / 180.),
0);
uiDrawCloseFigure(p->Context);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGB(p->Context, 0x00, 0x80, 0xC0);
uiDrawMoveTo(p->Context, 300, 300);
uiDrawBezierTo(p->Context,
350, 320,
310, 390,
435, 372);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
}
static uintmax_t handlerHScrollMax(uiAreaHandler *a, uiArea *area)
{/* TODO
WCHAR c[50];
GetWindowTextW(nhspinb, c, 50);
return _wtoi(c);
*/}
static uintmax_t handlerVScrollMax(uiAreaHandler *a, uiArea *area)
{/* TODO
WCHAR c[50];
GetWindowTextW(nvspinb, c, 50);
return _wtoi(c);
*/}
// areaUpdateScroll(area);
@interface appDelegate : NSObject<NSApplicationDelegate, NSTextFieldDelegate>
@end
@implementation appDelegate
- (void)controlTextDidChange:(NSNotification *)note
{
areaUpdateScroll(area);
}
- (void)applicationDidFinishLaunching:(NSApplication *)app
{
NSWindow *mainwin;
NSView *contentView;
NSView *areav;
NSArray *constraints;
NSDictionary *views;
mainwin = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 500, 500)
styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
backing:NSBackingStoreBuffered
defer:YES];
contentView = [mainwin contentView];
area = newArea((uiAreaHandler *) (&h));
areav = areaGetView(area);
[areav setTranslatesAutoresizingMaskIntoConstraints:NO];
[contentView addSubview:areav];
views = @{
@"areav": areav,
};
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[areav]-|"
options:0
metrics:nil
views:views];
[contentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[areav]-|"
options:0
metrics:nil
views:views];
[contentView addConstraints:constraints];
[mainwin makeKeyAndOrderFront:nil];
}
@end
int main(void)
{
NSApplication *app;
h.ah.Draw = handlerDraw;
h.ah.HScrollMax = handlerHScrollMax;
h.ah.VScrollMax = handlerVScrollMax;
app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
[app setDelegate:[appDelegate new]];
[app run];
return 0;
}

138
macarea/alt/ui.h Normal file
View File

@ -0,0 +1,138 @@
// 4 september 2015
typedef struct uiArea uiArea;
typedef struct uiAreaHandler uiAreaHandler;
typedef struct uiAreaDrawParams uiAreaDrawParams;
typedef struct uiDrawContext uiDrawContext;
struct uiAreaHandler {
void (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *);
uintmax_t (*HScrollMax)(uiAreaHandler *, uiArea *);
uintmax_t (*VScrollMax)(uiAreaHandler *, uiArea *);
};
struct uiAreaDrawParams {
uiDrawContext *Context;
intmax_t ClientWidth;
intmax_t ClientHeight;
intmax_t ClipX;
intmax_t ClipY;
intmax_t ClipWidth;
intmax_t ClipHeight;
int DPIX;
int DPIY;
intmax_t HScrollPos;
intmax_t VScrollPos;
};
// TODO proper sources
// TODO dotting/dashing
typedef struct uiDrawStrokeParams uiDrawStrokeParams;
typedef enum uiDrawLineCap uiDrawLineCap;
typedef enum uiDrawLineJoin uiDrawLineJoin;
typedef enum uiDrawFillMode uiDrawFillMode;
enum uiDrawLineCap {
uiDrawLineCapFlat,
uiDrawLineCapRound,
uiDrawLineCapSquare,
};
enum uiDrawLineJoin {
uiDrawLineJoinMiter,
uiDrawLineJoinRound,
uiDrawLineJoinBevel,
};
// this is the default for botoh cairo and GDI
// Core Graphics doesn't explicitly specify a default, but NSBezierPath allows you to choose one, and this is the initial value
// so we're good to use it too!
#define uiDrawDefaultMiterLimit 10.0
enum uiDrawFillMode {
uiDrawFillModeWinding,
// TODO rename to EvenOdd?
uiDrawFillModeAlternate,
};
struct uiDrawStrokeParams {
uiDrawLineCap Cap;
uiDrawLineJoin Join;
intmax_t Thickness;
double MiterLimit;
};
void uiDrawBeginPathRGB(uiDrawContext *, uint8_t, uint8_t, uint8_t);
// TODO verify these aren't alpha premultiplied anywhere
void uiDrawBeginPathRGBA(uiDrawContext *, uint8_t, uint8_t, uint8_t, uint8_t);
void uiDrawMoveTo(uiDrawContext *, intmax_t, intmax_t);
void uiDrawLineTo(uiDrawContext *, intmax_t, intmax_t);
void uiDrawRectangle(uiDrawContext *, intmax_t, intmax_t, intmax_t, intmax_t);
// notes: angles are both relative to 0 and go counterclockwise
void uiDrawArcTo(uiDrawContext *, intmax_t, intmax_t, intmax_t, double, double, int);
// TODO behavior when there is no initial point on Windows and OS X
void uiDrawBezierTo(uiDrawContext *, intmax_t, intmax_t, intmax_t, intmax_t, intmax_t, intmax_t);
void uiDrawCloseFigure(uiDrawContext *);
void uiDrawStroke(uiDrawContext *, uiDrawStrokeParams *);
void uiDrawFill(uiDrawContext *, uiDrawFillMode);
// path functions
// cairo gdi core graphics
// move_to MoveToEx MoveToPoint
// line_to LineTo AddLineToPoint
// arc Arc/ArcTo/AngleArc/Pie AddArc/AddArcToPoint/AddEllipseInRect
// arc_negative Arc/ArcTo/AngleArc/Pie AddArc/AddArcToPoint
// curve_to PolyBezier/PolyBezierTo AddCurveToPoint
// rectangle Rectangle AddRect
// [arc functions?] Chord [arc functions?]
// [combination] RoundRect [same way as cairo?]
// [TODO pango] TextOut/ExtTextOut [TODO core text]
// [TODO] [TODO] AddQuadCurveToPoint
// on sources:
// cairo:
// - RGB
// - RGBA
// - images
// - linear gradients, RGB or RGBA
// - rounded gradients, RGB or RGBA
// gdi:
// - RGB
// - hatches
// - images
// we can create a linear gradient image, but RGB only, and of finite size
// core graphics:
// - arbitrary patterns
// - solid colors, arbitrary spaces
// - shadows
// arcs
// cairo_arc/arc_negative
// - parameters: center, radius, angle1 radians, angle2 radins
// - if angle2 < angle1, TODO
// - if angle2 > angle1, TODO
// - line segment from current point to beginning of arc
// - arc: clockwise, arc_negative: counterclockwise
// - circular
// GDI Arc/Pie
// - parameters: bounding box, start point, end point
// - current position not used/changed
// - either clockwise or counterclockwise
// - elliptical
// GDI ArcTo
// - same as Arc except line segment from current point to beginning of arc
// - there does not appear to be a PieTo
// GDI AngleArc
// - parameters: center, radius, angle1 degrees, angle2 degrees
// - line segment from current position to beginning of arc
// - counterclockwise
// - circular
// - can be used to draw pies too; MSDN example demonstrates