CIETriangleImpl.mesa
Written by Maureen Stone on August 15, 1985 11:07:00 pm PDT
Last Edited by: Beach, February 13, 1984 1:45:05 pm PST
DIRECTORY
CIETriangle,
Imager USING [MaskVector, MaskStrokeTrajectory, black, white, SetColor, MaskRectangle],
ImagerPath USING [MoveTo, LineTo, Trajectory],
InputFocus USING [ReleaseButtons, CaptureButtons],
LinearSystem USING [Matrix2, Column2, Solve2],
Process USING [Milliseconds, SetTimeout, MsecToTicks, Detach],
Rope USING [ROPE],
TIPUser USING [TIPScreenCoords, InstantiateNewTIPTable],
ViewerClasses USING [Viewer, ViewerClass, ViewerClassRec, ViewerRec, PaintProc, GetProc, SetProc, NotifyProc],
ViewerOps USING [CreateViewer, PaintViewer, MouseInViewer, RegisterViewerClass, SetViewer],
WindowManager USING [RestoreCursor];
CIETriangleImpl: CEDAR MONITOR
IMPORTS Imager, ImagerPath, InputFocus, LinearSystem, Process, TIPUser, ViewerOps, WindowManager
EXPORTS CIETriangle =
BEGIN OPEN CIETriangle;
SliderState: TYPE = {inputDisabled, inputEnabled};
Reason: TYPE = {move, set, abort};
Line: TYPE=RECORD[a,b,c: REAL, vx,vy: REAL];
Vector: TYPE=RECORD[x,y: REAL];  --vx and vy are the opposite vertex. used for PinToLine
TriangleData: TYPE = REF TriangleDataRec;
TriangleDataRec: TYPE = RECORD [
proc: CIETriangleProc,
lineGR: Line, --in viewer coordinates
lineRB: Line,
lineBG: Line,
path: ImagerPath.Trajectory,
scale: REAL, --cie to viewer
locX,locY: REAL,  --value in viewer coordinates
oldX, oldY: REAL ← 0, --old locX, old locY
mouseX, mouseY: REAL ← 0, --position of mouse in triangle (mouse values)
savedX, savedY: REAL ← 0,  --postion for abort (mouse values)
reason: Reason ← move,     -- reason for calling client proc
oldReason: Reason ← move,    -- reason for calling client proc the last time
state: SliderState ← inputDisabled  -- indicates whether mouse input is being accepted
];
Create: PUBLIC PROCEDURE [info: ViewerClasses.ViewerRec ← [], xr,yr,xg,yg,xb,yb,xw,yw, scale: REAL, proc: CIETriangleProc] RETURNS [cieTriangle: Viewer] = {
path: ImagerPath.Trajectory ← ImagerPath.MoveTo[[xr*scale,yr*scale]];
path ← ImagerPath.LineTo[path, [xg*scale,yg*scale]];
path ← ImagerPath.LineTo[path, [xb*scale,yb*scale]];
info.data ← NEW[TriangleDataRec ← [
proc: proc,
scale: scale,
lineGR: MakeLine[scale, xg,yg,xr-xg,yr-yg, xb,yb],
lineRB: MakeLine[scale, xr,yr,xb-xr,yb-yr, xg,yg],
lineBG: MakeLine[scale, xb,yb,xg-xb,yg-yb, xr,yr],
locX: xw*scale, locY: yw*scale,
path: path
]];
info.scrollable ← FALSE;
info.border ← TRUE;
cieTriangle← ViewerOps.CreateViewer[flavor: $CIETriangle, info: info];
};
Reinitialize: PUBLIC PROCEDURE [cieTriangle: Viewer, xr,yr,xg,yg,xb,yb,xw,yw: REAL] = {
reinitializes the phosphor chromaticities
data: TriangleData ← NARROW[cieTriangle.data];
data.path ← ImagerPath.MoveTo[[xr*data.scale,yr*data.scale]];
data.path ← ImagerPath.LineTo[data.path, [xg*data.scale,yg*data.scale]];
data.path ← ImagerPath.LineTo[data.path, [xb*data.scale,yb*data.scale]];
data.lineGR ← MakeLine[data.scale, xg,yg,xr-xg,yr-yg, xb,yb];
data.lineRB ← MakeLine[data.scale, xr,yr,xb-xr,yb-yr, xg,yg];
data.lineBG ← MakeLine[data.scale, xb,yb,xg-xb,yg-yb, xr,yr];
data.locX ← xw*data.scale; data.locY ← yw*data.scale;
ViewerOps.PaintViewer[viewer: cieTriangle, hint: client, clearClient: TRUE, whatChanged: $newValue];
data.proc[self: cieTriangle, x: data.locX/data.scale, y: data.locY/data.scale];
};
GetContents: PUBLIC PROCEDURE [cieTriangle: Viewer] RETURNS [x,y: REAL] = {
returns a value in 0..1
data: TriangleData ← NARROW[cieTriangle.data];
RETURN[x: data.locX/data.scale, y: data.locY/data.scale];
};
SetContents: PUBLIC PROCEDURE [cieTriangle: Viewer, x,y: REAL] = {
takes a value in 0..1
data: REF Vector ← NEW[Vector ← [x:x, y:y]];
ViewerOps.SetViewer[viewer: cieTriangle, data: data];
};
--Utilities
MakeLine: PROCEDURE [scale: REAL, fromX,fromY,dx,dy, vx, vy: REAL ← 0] RETURNS [Line] = {
line: Line ← [a: -dy, b: dx, c: 0, vx: scale* vx, vy: scale*vy];
line.c ← -scale*(line.a*fromX+line.b*fromY);
RETURN[line];
};
ViewerProcs
debug: BOOLEANTRUE;
CIETrianglePaint: PRIVATE ViewerClasses.PaintProc = {
w: REAL = 2;
data: TriangleData ← NARROW[self.data];
drawBox: PROC [cx,cy: REAL] = {
Imager.MaskRectangle[context, [cx-w, cy-w, 2*w, 2*w]];
};
SELECT whatChanged FROM
$newValue => {
Imager.SetColor[context, Imager.white];
drawBox[cx: data.oldX, cy: data.oldY];
};
ENDCASE => NULL;
Imager.SetColor[context, Imager.black];
Imager.MaskRectangle[context, [data.locX-w, data.locY-w, 2*w, 2*w]];
Imager.MaskStrokeTrajectory[context: context, trajectory: data.path, closed: TRUE];
IF debug THEN {
DrawLine: PROC [line: Line] = {
Imager.MaskVector[context, [0, -line.c/line.b], [data.scale, -(line.c+data.scale*line.a)/line.b]];
};
DrawLine[data.lineGR];
DrawLine[data.lineRB];
DrawLine[data.lineBG];
};
data.oldX ← data.locX;
data.oldY ← data.locY;
};
CIETriangleGet: PRIVATE ViewerClasses.GetProc = {
triangleData: TriangleData ← NARROW[self.data];
vec: REF Vector ← NEW[Vector ← [x: triangleData.locX/triangleData.scale, y: triangleData.locY/triangleData.scale]];
RETURN[vec];
};
CIETriangleSet: PRIVATE ENTRY ViewerClasses.SetProc = {
vec: REF Vector ← NARROW[data];
triangleData: TriangleData ← NARROW[self.data];
SetTriangleValue[triangleData, vec.x*triangleData.scale, vec.y*triangleData.scale];
ViewerOps.PaintViewer[viewer: self, hint: client, clearClient: FALSE, whatChanged: $newValue];
};
SetTriangleValue: PRIVATE PROCEDURE [data: TriangleData, x,y: REAL] = {
x,y are viewer coordinates
flag: CARDINAL ← 0;
Left: PROC[line: Line] RETURNS [BOOLEAN] = {
value: REAL ← line.a*x+line.b*y+line.c;
RETURN[value >= 0];
};
PinToLine: PROC[line: Line] = TRUSTED {
pline: Line ← MakeLine[scale: 1, fromX: x, fromY: y, dx: line.vx-x, dy: line.vy-y];
matrix: LinearSystem.Matrix2 ← [[line.a,line.b],[pline.a,pline.b]];
column: LinearSystem.Column2 ← [-line.c,-pline.c];
point: LinearSystem.Column2 ←LinearSystem.Solve2[matrix,column];
data.locX ← point[1];
data.locY ← point[2];
};
PinToVertex: PROC[line1,line2: Line] = TRUSTED {
matrix: LinearSystem.Matrix2 ← [[line1.a,line1.b],[line2.a,line2.b]];
column: LinearSystem.Column2 ← [-line1.c,-line2.c];
point: LinearSystem.Column2 ←LinearSystem.Solve2[matrix,column];
data.locX ← point[1];
data.locY ← point[2];
};
IF Left[data.lineGR] THEN flag ← 1;
IF Left[data.lineRB] THEN flag ← flag+2;
IF Left[data.lineBG] THEN flag ← flag+4;
SELECT flag FROM
0 => {data.locX ← x; data.locY ← y};
1 => PinToLine[data.lineGR];
2 => PinToLine[data.lineRB];
4 => PinToLine[data.lineBG];
3 => PinToVertex[data.lineGR, data.lineRB];
6 => PinToVertex[data.lineRB, data.lineBG];
5 => PinToVertex[data.lineBG, data.lineGR];
ENDCASE => ERROR;
};
CIETriangleNotify: PRIVATE ViewerClasses.NotifyProc = TRUSTED {
ENABLE UNWIND => { InputFocus.ReleaseButtons[]; WindowManager.RestoreCursor[] };
v: ViewerClasses.Viewer;
c: BOOLEAN;
mouse: TIPUser.TIPScreenCoords;
mouseX, mouseY: REAL;
data: TriangleData ← NARROW[self.data];
FOR list: LIST OF REF ANY ← input, list.rest UNTIL list = NIL DO
WITH list.first SELECT FROM
x: ATOM => SELECT x FROM
$Enable => {
InputFocus.CaptureButtons[proc: CIETriangleNotify, tip: cieTriangleClass.tipTable, viewer: self];
data.state ← inputEnabled;
data.savedX ← data.locX; data.savedY ← data.locX;
mouseX ← mouse.mouseX; mouseY ← mouse.mouseY;
SetNewTriangleValue[data: data, mouseX: mouseX, mouseY: mouseY, reason: move];
CreateSliderProcess[self];
};
$Abort => {
InputFocus.ReleaseButtons[];
data.state ← inputDisabled;
SetNewTriangleValue[data: data, mouseX: data.savedX, mouseY: data.savedY, reason: abort];
sliderProcessData.state ← dying;
};
$Move => IF data.state = inputEnabled THEN {
[v, c] ← ViewerOps.MouseInViewer[mouse];
IF v = self AND c THEN
{mouseX ← mouse.mouseX; mouseY ← mouse.mouseY}
ELSE {mouseX ← data.mouseX; mouseY ← data.mouseY};
SetNewTriangleValue[data: data, mouseX: mouseX, mouseY: mouseY, reason: move];
};
$Set => IF data.state = inputEnabled THEN {
reason: Reason ← set;
InputFocus.ReleaseButtons[];
data.state ← inputDisabled;
[v, c] ← ViewerOps.MouseInViewer[mouse];
IF v = self AND c THEN
{mouseX ← mouse.mouseX; mouseY ← mouse.mouseY}
ELSE {
mouseX ← data.savedX; mouseY ← data.savedY;
reason ← abort;
};
SetNewTriangleValue[data: data, mouseX: mouseX, mouseY: mouseY, reason: reason];
sliderProcessData.state ← dying;
};
ENDCASE => NULL;
z: TIPUser.TIPScreenCoords => mouse ← z;
ENDCASE => ERROR;
ENDLOOP;
};
SetNewTriangleValue: PRIVATE ENTRY PROCEDURE [data: TriangleData, mouseX,mouseY: REAL, reason: Reason] = {
data.mouseX ← mouseX;
data.mouseY ← mouseY;
data.reason ← reason;
};
process for smooth interaction
SliderProcessData: TYPE = REF SliderProcessDataRec;
SliderProcessDataRec: TYPE = RECORD [
triangle: Viewer ← NIL,
timerCondition: CONDITION,
state: SliderProcessState ← alive
];
SliderProcessState: TYPE = {alive, dying};
sliderProcessData: SliderProcessData ← NIL;
CreateSliderProcess: PRIVATE PROCEDURE [triangle: Viewer] = {
sliderProcessData ← NEW[SliderProcessDataRec];
sliderProcessData.triangle ← triangle;
TRUSTED {Process.Detach[FORK SliderProcess[sliderProcessData]]};
};
SliderProcess: PRIVATE PROCEDURE [myData: SliderProcessData] = {
UpdateSliderValue: PROCEDURE [self: Viewer] = {
data: TriangleData ← NARROW[self.data];
notifyClient: BOOLEAN;
reason: Reason;
x,y: REAL;
[notifyClient, reason, x,y] ← UpdateSlider[self];
IF data.proc # NIL AND notifyClient THEN data.proc[self: self, x: x/data.scale, y: y/data.scale];
};
timerPeriod: Process.Milliseconds = 40;
WHILE myData.state = alive DO
UpdateSliderValue[myData.triangle];
Wait[myData, timerPeriod];
ENDLOOP;
UpdateSliderValue[myData.triangle];
};
UpdateSlider: PRIVATE ENTRY PROCEDURE [self: Viewer] RETURNS [notifyClient: BOOLEAN, reason: Reason, x,y: REAL] = {
data: TriangleData ← NARROW[self.data];
notifyClient ← FALSE;
IF data.mouseX # data.oldX OR data.mouseY # data.oldY THEN {
notifyClient ← TRUE;
SetTriangleValue[data, data.mouseX, data.mouseY];
IF data.locX # data.oldX OR data.locY # data.oldY THEN {
notifyClient ← TRUE;
ViewerOps.PaintViewer[viewer: self, hint: client, clearClient: FALSE, whatChanged: $newValue];
};
};
IF data.reason # data.oldReason THEN notifyClient ← TRUE;
data.oldReason ← data.reason;
RETURN[notifyClient, data.reason, data.locX, data.locY];
};
Wait: PRIVATE ENTRY PROCEDURE [data: SliderProcessData, milliseconds: Process.Milliseconds] = {
TRUSTED {Process.SetTimeout[@data.timerCondition, Process.MsecToTicks[milliseconds]]};
WAIT data.timerCondition;
};
Initialization
cieTriangleClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: CIETrianglePaint,
get: CIETriangleGet,
set: CIETriangleSet,
notify: CIETriangleNotify,
tipTable: TIPUser.InstantiateNewTIPTable["Slider.tip"],
cursor: crossHairsCircle
]];
Register the CIETriangle class of viewer with the Window Manager
ViewerOps.RegisterViewerClass[$CIETriangle, cieTriangleClass];
END.