CIETriangleImpl.mesa
Written by Maureen Stone on April 9, 1985 4:54:47 pm PST
Last Edited by: Beach, February 13, 1984 1:45:05 pm PST
DIRECTORY
CIETriangle,
Graphics USING [Path, FlushPath, MoveTo, LineTo, Box, DrawBox, SetColor, DrawStroke, NewPath, SetCP, DrawTo],
GraphicsBasic USING [black, white],
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 Graphics, 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: Graphics.Path,
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: Graphics.Path ← Graphics.NewPath[size: 3];
Graphics.MoveTo[path, xr*scale,yr*scale];
Graphics.LineTo[path, xg*scale,yg*scale];
Graphics.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];
Graphics.FlushPath[data.path];
Graphics.MoveTo[data.path, xr*data.scale,yr*data.scale];
Graphics.LineTo[data.path, xg*data.scale,yg*data.scale];
Graphics.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: BOOLEAN ← TRUE;
CIETrianglePaint:
PRIVATE ViewerClasses.PaintProc = {
w: REAL = 2;
data: TriangleData ← NARROW[self.data];
drawBox:
PROC [cx,cy:
REAL] = {
box: Graphics.Box ← [xmin: cx-w, xmax: cx+w, ymin: cy-w, ymax: cy+w];
Graphics.DrawBox[context, box];
};
SELECT whatChanged
FROM
$newValue => {
Graphics.SetColor[context, GraphicsBasic.white];
drawBox[cx: data.oldX, cy: data.oldY];
};
ENDCASE => NULL;
Graphics.SetColor[context, GraphicsBasic.black];
drawBox[cx: data.locX, cy: data.locY];
Graphics.DrawStroke[self: context, path: data.path, closed: TRUE];
IF debug
THEN {
DrawLine:
PROC [line: Line] = {
Graphics.SetCP[context, 0, -line.c/line.b];
Graphics.DrawTo[context, 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.