MODULE Slider; IMPORT Math, Text, Color, R2, Geometry, PS, Angle, Bezier, Circle, Type, Rect, TypeLinesC; (* A module for drawing sliders and associating them with constraints. *) (* A slider is represented by three points, "a", "b", and "c". The points "a" and "c" determine the endpoints of the slider, and the point "b" determines the location of its ``thumb''. In particular, "a" determines the axis of the slider and one of its endpoints, "c" determines the sizes of the tick marks at the end of the slider and one of its endpoints. The projection of "b" onto the slider's axis is the location of the slider's thumb. The location of the thumb determine's the slider's value. The value is defined as the fraction of the distance the thumb is from "a" to "c". Hence, when the thumb is within the bounds of the slider, the slider's value is in the interval [0, 1]. *) (* \section{Slider Values} *) FUNC res = Value(a, b, c) IS res * (CAR(c) - CAR(a)) = CAR(b) - CAR(a) END; (* "res" is the value of the slider determined by the points "a", "b", and "c". *) FUNC b = ThumbPoint(a, c, t) IS b = (t, 0) REL (a, c) END; (* "b" satisfies "t = Value(a,b,c)". *) (* \section{Drawing Sliders} *) CONST Invisible = 0, Visible = 1, DefaultVisibility = Visible; (* This module maintains a ``current visibility'', which determines whether the "Draw" procedure draws anything or not. *) PRIVATE VAR visibility := DefaultVisibility; PROC SetVisibility(vis) IS visibility := vis END; UI SetTool(SetVisibility); (* Set the current visibility to "vis", which should be either "Invisible" or "Visible". *) UI Param(SetVisibility, DefaultVisibility); UI Param(SetVisibility, Invisible); UI Param(SetVisibility, Visible); CONST HideValue = 0, ShowValue = 1, DefaultValueVis = ShowValue; (* This module maintains a current ``value visibility'', which controls whether the "Draw" procedure shows the slider's value. *) PRIVATE VAR valueVis := DefaultValueVis; PROC SetValueVis(vis) IS valueVis := vis END; UI SetTool(SetValueVis); (* Set the current value visibility to "vis", which should be either "HideValue" or "ShowValue". *) UI Param(SetValueVis, DefaultValueVis); UI Param(SetValueVis, HideValue); UI Param(SetValueVis, ShowValue); PRIVATE CONST Gap = 5, ThumbW = 5; PRIVATE PROC DrawTrackHor(a, b) IS VAR c, d, e, f IN c := Geometry.HorVer(a, b); d := Geometry.HorVer(b, a); e := (-1, -0) REL (a, d); f := (-1, -0) REL (c, b); SAVE PS IN PS.SetEndStyle(PS.SquareEnds); PS.MoveTo(a); PS.LineTo(c); PS.MoveTo(d); PS.LineTo(e); PS.MoveTo(b); PS.LineTo(f); PS.Stroke() END END END; PRIVATE PROC DrawThumbHor0(a, b) IS VAR c IN c := R2.PlusX(b, ThumbW / 2); Rect.DrawC(a, c); SAVE PS IN PS.SetColor(Color.Grey75); PS.Fill() END; PS.Stroke() END END; PRIVATE PROC DrawThumbHor(a, b, c) IS VAR d, e IN d := Geometry.HorVer(a, b); e := Geometry.HorVer(c, b); DrawThumbHor0(d, e) END END; PRIVATE CONST Decimal = Text.GetChar(".", 0); PRIVATE PROC DrawValue(a, b, c) IS VAR d, e, val, dotPos, str IN d := Geometry.HorVer(c, a); e := Geometry.Mid(d, c); val := Text.FromNum(Value(a, b, c), 1); dotPos := Text.FindChar(val, Decimal); IF dotPos = -1 -> val := val & ".00" | DO Text.Length(val) - dotPos <= 2 -> val := val & "0" OD FI; str := "Value = " & val; IF CDR(c) > CDR(a) -> Type.C(R2.PlusY(d, Gap), "0"); Type.C(R2.PlusY(c, Gap), "1"); Type.C(R2.PlusY(e, Gap), str) | TypeLinesC.North(d, "0"); TypeLinesC.North(c, "1"); TypeLinesC.North(e, str) FI END END; PROC Draw(a, b, c) IS IF visibility = Visible -> SAVE PS IN PS.SetWidth(1.5); DrawTrackHor(a, c); DrawThumbHor(a, b, c); IF valueVis = ShowValue -> DrawValue(a, b, c) | SKIP FI END | SKIP FI END; UI PointTool(Draw); (* If the current visibility is "Visible", draw a horizontal slider with endpoints "a" and "c", and thumb position "b". The horizontal axis of the slider passes through "a", and its vertical extent is determined by the vertical position of "c". The point "b" controls the horizontal position of the slider's thumb. If the current value visibility is "ShowValue", the slider is annotated with its current value, rendered in the current font. The value is printed above or below the slider's thumb as "c" is above or below the slider's axis. *) (* \section{Constraining Sliders} The following predicates all constrain a point "p" to lie along a geometric shape such as a line or circle according to the value of a slider "a", "b", "c". In all of these predicates, the slider points "a", "b", "c" are the first three arguments, and the constrained point "p" is the last argument (with the exception of the "OnLine" constraint). *) PRED OnLine(a, b, c, l0, p, l1) IS (E t = Value(a, b, c) :: p = (t, 0) REL (l0, l1)) END; UI PointTool(OnLine); (* Constrains the point "p" to be the point on the segment with endpoints "l0" and "l1" whose relative position along that segment is the value of the slider "a", "b", "c". *) PRED OnCurve(a, b, c, b0, b1, b2, b3, p) IS (E t = Value(a, b, c) :: p = Bezier.AtT(b0, b1, b2, b3, t)) END; UI PointTool(OnCurve); (* Constrains the point "p" to be the point on the Bezier curve determined by "b0", "b1", "b2", and "b3" whose relative position along that curve is the value of the slider "a", "b", "c". *) PRIVATE PRED OnCircle0(a, b, c, c0, c1, p, k) IS (E t = k * Value(a, b, c) :: p = (COS(t), SIN(t)) REL (c0, c1)) END; PRED OnCircle(a, b, c, c0, c1, p) IS OnCircle0(a, b, c, c0, c1, p, -2 * Math.Pi) END; PRED OnCircleCC(a, b, c, c0, c1, p) IS OnCircle0(a, b, c, c0, c1, p, 2 * Math.Pi) END; UI PointTool(OnCircle); UI PointTool(OnCircleCC); (* The point "p" is on the circle with center "c0" and radius point "c1". Its position around the circle measured in the clockwise and counter-clockwise direction, respectfully, from "c1" is proportional to the value of the slider "a", "b", "c". *) PRED OnSemi(a, b, c, c0, c1, p) IS OnCircle0(a, b, c, c0, c1, p, -Math.Pi) END; PRED OnSemiCC(a, b, c, c0, c1, p) IS OnCircle0(a, b, c, c0, c1, p, Math.Pi) END; UI PointTool(OnSemi); UI PointTool(OnSemiCC); (* The point "p" is on the semi-circle with center "c0" and radius point "c1". Its position around the semi-circle measured in the clockwise and counter-clockwise direction, respectfully, from "c1" is proportional to the value of the slider "a", "b", "c". *) PRED OnQuarter(a, b, c, c0, c1, p) IS OnCircle0(a, b, c, c0, c1, p, -Math.Pi / 2) END; PRED OnQuarterCC(a, b, c, c0, c1, p) IS OnCircle0(a, b, c, c0, c1, p, Math.Pi / 2) END; UI PointTool(OnQuarter); UI PointTool(OnQuarterCC); (* The point "p" is on the quarter-circle with center "c0" and radius point "c1". Its position around the quarter-circle measured in the clockwise and counter-clockwise direction, respectfully, from "c1" is proportional to the value of the slider "a", "b", "c". *) PRED OnArc(a, b, c, a1, a2, a3, p) IS OnCircle0(a, b, c, a2, a1, p, Angle.CC(a1, a2, a3)) END; UI PointTool(OnArc); (* The point "p" is on the directed circular arc with center "a2", starting at "a1" and ending at the line through "a1" and "a3". The position of "p" along the arc is proportional to the value of the slider "a", "b", "c". *) PRED AtAngle1(a, b, c, p, q, max) IS (E t = Value(a, b, c), delta = R2.Minus(q, p), dx, dy :: delta = (dx, dy) AND t * max = ATAN(dy, dx)) END; (* The ray "pq" forms an angle in the interval "[0, max]" to the horizontal. The angle in that interval is proportional to the value of the slider "a", "b", "c". *) PRED AtAngle2(a, b, c, p, q, min, max) IS (E t = Value(a, b, c), t2 = t * (max - min) + min, delta = R2.Minus(q, p), dx, dy :: delta = (dx, dy) AND t2 = ATAN(dy, dx)) END; (* The ray "pq" forms an angle in the interval "[min, max]" to the horizontal. The angle in that interval is proportional to the value of the slider "a", "b", "c". *) (* \section{Saving/Restoring State} *) PRIVATE VAR history := NIL; PROC Save() IS history := ((visibility, valueVis), history) END; PROC Restore() IS VAR head IN head := CAR(history); visibility := CAR(head); valueVis := CDR(head) END; history := CDR(history) END; UI PointTool(Save); UI PointTool(Restore); (* Save/restore the current module state. *)