quakeforge/tools/Forge/Bundles/MapEdit/ZView.m
Bill Currie fb65cd2b44 Cache needed fonts.
Obtaining a font has become rather expensive in recent gnustep, so cache
fonts, obtaining new ones only when needed rather than every redraw. Makes
things snappy again :)
2011-04-16 12:17:23 +09:00

807 lines
17 KiB
Objective-C

#include "QF/sys.h"
#include "ZView.h"
#include "ZScrollView.h"
#include "QuakeEd.h"
#include "Map.h"
#include "XYView.h"
#include "CameraView.h"
id zview_i;
id zscrollview_i, zscalemenu_i, zscalebutton_i;
float zplane;
float zplanedir;
extern NSBezierPath *path;
@implementation ZView
/*
==================
initWithFrame:
==================
*/
- (id) initWithFrame: (NSRect)frameRect
{
NSPoint pt;
NSBezierPath *path;
font = [[NSFont systemFontOfSize: 10] retain];
path = checker = [NSBezierPath new];
[path setLineWidth: 0.3];
[path moveToPoint: NSMakePoint (-16, -16)];
[path relativeLineToPoint: NSMakePoint (32, 32)];
[path moveToPoint: NSMakePoint (-16, 16)];
[path relativeLineToPoint: NSMakePoint (32, -32)];
origin[0] = 0.333;
origin[1] = 0.333;
[super initWithFrame: frameRect];
[self allocateGState];
[self clearBounds];
zview_i = self;
scale = 1;
// initialize the pop up menus
zscalebutton_i = [[NSPopUpButton alloc] init];
[zscalebutton_i setTarget: self];
[zscalebutton_i setAction: @selector (scaleMenuTarget:)];
[zscalebutton_i addItemWithTitle: @"12.5%"];
[zscalebutton_i addItemWithTitle: @"25%"];
[zscalebutton_i addItemWithTitle: @"50%"];
[zscalebutton_i addItemWithTitle: @"75%"];
[zscalebutton_i addItemWithTitle: @"100%"];
[zscalebutton_i addItemWithTitle: @"200%"];
[zscalebutton_i addItemWithTitle: @"300%"];
[zscalebutton_i selectItemAtIndex: 4];
// initialize the scroll view
zscrollview_i = [[ZScrollView alloc]
initWithFrame: frameRect
button1: zscalebutton_i];
[zscrollview_i setAutoresizingMask: NSViewWidthSizable |
NSViewHeightSizable];
[zscrollview_i setDocumentView: self];
[_super_view setBoundsOrigin: NSMakePoint (0, 0)];
minheight = 0;
maxheight = 64;
pt.x = -[_super_view bounds].size.width / 2;
pt.y = -128;
[self newRealBounds];
[self setOrigin: pt scale: 1];
return zscrollview_i;
}
- (BOOL) isOpaque
{
return YES;
}
- (id) setXYOrigin: (NSPoint *)pt
{
origin[0] = pt->x + 0.333;
origin[1] = pt->y + 0.333;
return self;
}
- (float) currentScale
{
return scale;
}
/*
===================
setOrigin:scale:
===================
*/
- (id) setOrigin: (NSPoint)pt scale: (float)sc
{
NSRect sframe;
NSRect bounds;
NSRect size;
// calculate the area visible in the cliprect
scale = sc;
bounds = [_super_view bounds];
bounds.origin = pt;
bounds.size.width /= 1;
bounds.size.height /= scale;
size = NSMakeRect (0, oldminheight, 1, oldmaxheight - oldminheight);
// union with the realbounds
bounds = NSUnionRect (size, bounds);
// redisplay everything
bounds.origin = NSMakePoint (-bounds.size.width / 2, bounds.origin.y);
sframe = bounds;
sframe.origin.x *= 1;
sframe.origin.y *= scale;
sframe.size.width *= 1;
sframe.size.height *= scale;
// size this view
[quakeed_i disableFlushWindow];
[self setFrame: sframe bounds: bounds scale: NSMakeSize (1, scale)];
// scroll and scale the clip view
pt.x *= 1;
pt.y *= scale;
[_super_view setBoundsOrigin: pt];
[quakeed_i enableFlushWindow];
[zscrollview_i display];
return self;
}
/*
====================
scaleMenuTarget:
Called when the scaler popup on the window is used
====================
*/
- (id) scaleMenuTarget: sender
{
NSRect rect;
NSPoint mid, org, orig;
float nscale;
nscale = [[sender titleOfSelectedItem] floatValue] / 100;
if (nscale == scale)
return NULL;
// keep the center of the view constant
rect = [_super_view bounds];
mid.x = rect.size.width / 2;
mid.y = rect.size.height / 2;
org.x = (rect.origin.x + mid.x) / 1;
org.y = (rect.origin.y + mid.y) / scale;
orig.x = org.x - mid.x / 1;
orig.y = org.y - mid.y / nscale;
[self setOrigin: orig scale: nscale];
return self;
}
- (id) clearBounds
{
topbound = 999999;
bottombound = -999999;
return self;
}
- (id) getBounds: (float *)top: (float *)bottom
{
*top = topbound;
*bottom = bottombound;
return self;
}
/*
==================
addToHeightRange:
==================
*/
- (id) addToHeightRange: (float)height
{
if (height < minheight)
minheight = height;
if (height > maxheight)
maxheight = height;
return self;
}
/*
==================
newSuperBounds
When _super_view is resized
==================
*/
- (id) newSuperBounds
{
oldminheight++;
[self newRealBounds];
return self;
}
/*
===================
newRealBounds
Should only change the scroll bars, not cause any redraws.
If realbounds has shrunk, nothing will change.
===================
*/
- (id) newRealBounds
{
NSRect bounds;
NSRect sframe;
NSRect size;
NSClipView *cv = (NSClipView *) _super_view;
if (minheight == oldminheight && maxheight == oldmaxheight)
return self;
oldminheight = minheight;
oldmaxheight = maxheight;
minheight -= 16;
maxheight += 16;
// calculate the area visible in the cliprect
bounds = [cv bounds];
bounds.size.width /= 1;
bounds.size.height /= scale;
size = NSMakeRect (0, minheight, 1, maxheight - minheight);
bounds = NSUnionRect (size, bounds);
sframe = bounds;
sframe.size.width *= 1;
sframe.size.height *= scale;
// size this view
[quakeed_i disableFlushWindow];
[self setFrame: sframe bounds: bounds scale: NSMakeSize (1, scale)];
[[_super_view superview] reflectScrolledClipView: cv];
[quakeed_i enableFlushWindow];
[[zscrollview_i verticalScroller] display];
return self;
}
/*
============
drawGrid
Draws tile markings every 64 units, and grid markings at the grid scale if
the grid lines are >= 4 pixels apart
Rect is in global world (unscaled) coordinates
============
*/
- (id) drawGrid: (NSRect)rect
{
int y, stopy;
float top, bottom;
int left, right;
int gridsize;
BOOL showcoords;
NSMutableDictionary *attribs = [NSMutableDictionary dictionary];
showcoords = [quakeed_i showCoordinates];
gridsize = [xyview_i gridsize];
left = _bounds.origin.x;
right = 24;
bottom = rect.origin.y - 1;
top = rect.origin.y + rect.size.height + 2;
// grid
// can't just divide by grid size because of negetive coordinate
// truncating direction
if (gridsize >= 4 / scale) {
y = floor (bottom / gridsize);
stopy = floor (top / gridsize);
y *= gridsize;
stopy *= gridsize;
if (y < bottom)
y += gridsize;
[path removeAllPoints];
for ( ; y <= stopy; y += gridsize) {
if (y & 31) {
[path moveToPoint: NSMakePoint (left, y)];
[path lineToPoint: NSMakePoint (right, y)];
}
}
// endUserPath (upath, dps_ustroke);
[[NSColor colorWithCalibratedRed: 0.8
green: 0.8
blue: 1.0
alpha: 1.0] set]; // thin grid color
[path stroke];
}
// half tiles
y = floor (bottom / 32);
stopy = floor (top / 32);
if (!(((int) y + 4096) & 1))
y++;
y *= 32;
stopy *= 32;
if (stopy >= top)
stopy -= 32;
[path removeAllPoints];
for ( ; y <= stopy; y += 64) {
[path moveToPoint: NSMakePoint (left, y)];
[path lineToPoint: NSMakePoint (right, y)];
}
// endUserPath (upath, dps_ustroke);
[[NSColor colorWithCalibratedWhite: 12.0 / 16.0 alpha: 1.0]
set];
[path stroke];
// tiles
y = floor (bottom / 64);
stopy = floor (top / 64);
y *= 64;
stopy *= 64;
if (y < bottom)
y += 64;
if (stopy >= top)
stopy -= 64;
[path removeAllPoints];
[[NSColor colorWithCalibratedWhite: 0.0 / 16.0 alpha: 1.0] set]; // for text
[font set];
for ( ; y <= stopy; y += 64) {
if (showcoords) {
NSString *s = [NSString stringWithFormat: @"%i", y];
[s drawAtPoint: NSMakePoint (left, y) withAttributes: attribs];
}
[path moveToPoint: NSMakePoint (left + 24, y)];
[path lineToPoint: NSMakePoint (right, y)];
}
// divider
[path moveToPoint: NSMakePoint (0, _bounds.origin.y)];
[path lineToPoint: NSMakePoint (0, _bounds.origin.y + _bounds.size.height)];
// endUserPath (upath, dps_ustroke);
[[NSColor colorWithCalibratedWhite: 10.0 / 16.0 alpha: 1.0] set];
[path stroke];
// origin
[[NSColor colorWithCalibratedWhite: 4.0 / 16.0 alpha: 1.0] set];
[path removeAllPoints];
[path setLineWidth: 5];
[path moveToPoint: NSMakePoint (right, 0)];
[path lineToPoint: NSMakePoint (left, 0)];
[path stroke];
[path setLineWidth: 1];
return self;
}
- (id) drawZplane
{
[[NSColor colorWithCalibratedRed: 0.2 green: 0.2 blue: 0.0 alpha: 1.0] set];
[path appendBezierPathWithArcWithCenter: NSMakePoint (0, zplane)
radius: 4
startAngle: 0
endAngle: 180];
[path fill];
return self;
}
/*
===============================================================================
drawSelf
===============================================================================
*/
- (void) drawRect: (NSRect)rect
{
// NSRect visRect;
// Sys_Printf("ZView:drawRect\n");
minheight = 999999;
maxheight = -999999;
// allways draw the entire bar
// visRect =[self visibleRect];
rect = [self visibleRect];
[quakeed_i zNoRestore: rect];
// erase window
NSEraseRect (rect);
// draw grid
[self drawGrid: rect];
// draw zplane
[self drawZplane]; //FIXME zplane doesn't do anything yet
// draw all entities
[map_i makeUnselectedPerform: @selector (ZDrawSelf)];
// possibly resize the view
[self newRealBounds];
}
/*
==============
XYDrawSelf
==============
*/
- (void) XYDrawSelf
{
NSBezierPath *path;
NSAffineTransform *trans;
[[NSColor colorWithCalibratedRed: 0 green: 0.5 blue: 1.0 alpha: 1.0] set];
trans = [NSAffineTransform transform];
[trans translateXBy: origin[0] yBy: origin[1]];
path = [checker copy];
[path transformUsingAffineTransform: trans];
[path stroke];
[path release];
}
/*
==============
getPoint: (NSPoint *)pt
==============
*/
- (id) getPoint: (NSPoint *)pt
{
pt->x = origin[0] + 0.333; // offset a bit to avoid edge cases
pt->y = origin[1] + 0.333;
return self;
}
- (id) setPoint: (NSPoint *)pt
{
origin[0] = pt->x;
origin[1] = pt->y;
return self;
}
/*
==============================================================================
MOUSE CLICKING
==============================================================================
*/
/*
================
dragLoop:
================
*/
static NSPoint oldreletive;
- (id) dragFrom: (NSEvent *)startevent
useGrid: (BOOL)ug
callback: (void (*)(float dy))callback
{
NSEvent *event;
NSPoint startpt, newpt;
NSPoint reletive, delta;
int gridsize;
gridsize = [xyview_i gridsize];
startpt = [startevent locationInWindow];
startpt = [self convertPoint: startpt fromView: NULL];
oldreletive.x = oldreletive.y = 0;
reletive.x = 0;
while (1) {
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask
| NSRightMouseUpMask | NSRightMouseDraggedMask
| NSApplicationDefinedMask;
event = [NSApp nextEventMatchingMask: eventMask untilDate: [NSDate
distantFuture]
inMode: NSEventTrackingRunLoopMode dequeue:
YES];
if ([event type] == NSLeftMouseUp || [event type] == NSRightMouseUp)
break;
newpt = [event locationInWindow];
newpt = [self convertPoint: newpt fromView: NULL];
reletive.y = newpt.y - startpt.y;
if (ug) // we want truncate-towards-0 behavior here
reletive.y = gridsize * (int) (reletive.y / gridsize);
if (reletive.y == oldreletive.y)
continue;
delta.y = reletive.y - oldreletive.y;
oldreletive = reletive;
callback (delta.y);
}
return self;
}
// ============================================================================
void
ZDragCallback (float dy)
{
sb_translate[0] = 0;
sb_translate[1] = 0;
sb_translate[2] = dy;
[map_i makeSelectedPerform: @selector (translate)];
[quakeed_i redrawInstance];
}
- (id) selectionDragFrom: (NSEvent *)theEvent
{
Sys_Printf ("dragging selection\n");
[self dragFrom: theEvent useGrid: YES callback: ZDragCallback];
[quakeed_i updateCamera];
return self;
}
// ============================================================================
void
ZScrollCallback (float dy)
{
NSRect basebounds;
NSPoint neworg;
float scale;
basebounds = [[zview_i superview] bounds];
basebounds = [zview_i convertRect: basebounds fromView: [zview_i
superview]];
neworg.y = basebounds.origin.y - dy;
scale = [zview_i currentScale];
oldreletive.y -= dy;
[zview_i setOrigin: neworg scale: scale];
}
- (id) scrollDragFrom: (NSEvent *)theEvent
{
Sys_Printf ("scrolling view\n");
[self dragFrom: theEvent useGrid: YES callback: ZScrollCallback];
return self;
}
// ============================================================================
void
ZControlCallback (float dy)
{
int i;
for (i = 0; i < numcontrolpoints; i++)
controlpoints[i][2] += dy;
[[map_i selectedBrush] calcWindings];
[quakeed_i redrawInstance];
}
- (BOOL) planeDragFrom: (NSEvent *)theEvent
{
NSPoint pt;
vec3_t dragpoint;
if ([map_i numSelected] != 1)
return NO;
pt = [theEvent locationInWindow];
pt = [self convertPoint: pt fromView: NULL];
dragpoint[0] = origin[0];
dragpoint[1] = origin[1];
dragpoint[2] = pt.y;
[[map_i selectedBrush] getZdragface: dragpoint];
if (!numcontrolpoints)
return NO;
Sys_Printf ("dragging brush plane\n");
pt = [theEvent locationInWindow];
pt = [self convertPoint: pt fromView: NULL];
[self dragFrom: theEvent useGrid: YES callback: ZControlCallback];
[[map_i selectedBrush] removeIfInvalid];
[quakeed_i updateCamera];
return YES;
}
// ============================================================================
/*
===================
mouseDown
===================
*/
- (void) mouseDown: (NSEvent *)theEvent
{
NSPoint pt;
int flags;
vec3_t p1;
pt = [theEvent locationInWindow];
pt = [self convertPoint: pt fromView: NULL];
p1[0] = origin[0];
p1[1] = origin[1];
p1[2] = pt.y;
flags =
[theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask |
NSAlternateKeyMask | NSCommandKeyMask);
// shift click to select / deselect a brush from the world
if (flags == NSShiftKeyMask) {
[map_i selectRay: p1: p1: NO];
return;
}
// alt click = set entire brush texture
if (flags == NSAlternateKeyMask) {
[map_i setTextureRay: p1: p1: YES];
return;
}
// control click = position view
if (flags == NSControlKeyMask) {
[cameraview_i setZOrigin: pt.y];
[quakeed_i updateAll];
[cameraview_i ZmouseDown: &pt flags: [theEvent modifierFlags]];
return;
}
// bare click to drag icons or new brush drag
if (flags == 0) {
// check eye
if ([cameraview_i ZmouseDown: &pt flags: [theEvent modifierFlags]])
return;
if ([map_i numSelected]) {
if (pt.x > 0) {
if ([self planeDragFrom: theEvent])
return;
}
[self selectionDragFrom: theEvent];
return;
}
}
Sys_Printf ("bad flags for click %x %g %g\n", flags, pt.x, pt.y);
NopSound ();
return;
}
/*
===================
rightMouseDown
===================
*/
- (void) rightMouseDown: (NSEvent *)theEvent
{
NSPoint pt;
int flags;
pt = [theEvent locationInWindow];
pt = [self convertPoint: pt fromView: NULL];
flags =
[theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask |
NSAlternateKeyMask | NSCommandKeyMask);
// click = scroll view
if (flags == 0)
[self scrollDragFrom: theEvent];
Sys_Printf ("bad flags for click\n");
NopSound ();
}
/*
===============================================================================
XY mouse view methods
===============================================================================
*/
/*
================
modalMoveLoop
================
*/
- (id) modalMoveLoop: (NSPoint *)basept
: (vec3_t)movemod
: converter
{
vec3_t originbase;
NSEvent *event;
NSPoint newpt;
vec3_t delta;
int i;
VectorCopy (origin, originbase);
// modal event loop using instance drawing
goto drawentry;
while ([event type] != NSLeftMouseUp) {
// calculate new point
newpt = [event locationInWindow];
newpt = [converter convertPoint: newpt fromView: NULL];
delta[0] = newpt.x - basept->x;
delta[1] = newpt.y - basept->y;
delta[2] = delta[1]; // height change
for (i = 0; i < 3; i++)
origin[i] = originbase[i] + movemod[i] * delta[i];
drawentry:
// instance draw new frame
[quakeed_i newinstance];
[self display];
unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
event = [NSApp nextEventMatchingMask: eventMask untilDate: [NSDate
distantFuture]
inMode: NSEventTrackingRunLoopMode dequeue:
YES];
}
// draw the brush back into the window buffer
// [xyview_i display];
return self;
}
/*
===============
XYmouseDown
===============
*/
- (BOOL) XYmouseDown: (NSPoint *)pt
{
vec3_t movemod;
if (fabs (pt->x - origin[0]) > 16 || fabs (pt->y - origin[1]) > 16)
return NO;
movemod[0] = 1;
movemod[1] = 1;
movemod[2] = 0;
[self modalMoveLoop: pt: movemod: xyview_i];
return YES;
}
@end