mirror of
https://github.com/PascalHarris/Mandy.git
synced 2026-10-25 01:02:18 +00:00
378 lines
13 KiB
C
378 lines
13 KiB
C
/*****
|
|
* mwInfo.c
|
|
*
|
|
* The "Get Info" window: a small palette-style window showing the
|
|
* current fractal's resolution, screen bit depth, render time (or
|
|
* elapsed time, while a render is still in progress), and its
|
|
* underlying mathematical parameters.
|
|
*
|
|
*****/
|
|
#include "mwInfo.h"
|
|
#include "mwWindow.h"
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#ifndef _Quickdraw_
|
|
#include <Quickdraw.h>
|
|
#endif
|
|
|
|
extern int width; /* which fractal is selected - see mwWindow.c */
|
|
|
|
#define kInfoWindowWidth 230
|
|
#define kInfoWindowHeight 140
|
|
#define kInfoWindowLeft 60
|
|
#define kInfoWindowTop 60
|
|
#define kInfoWindowTitle "\pFractal Info"
|
|
|
|
#define kInfoFontSize 9
|
|
|
|
#define kLeftMargin 12
|
|
#define kTopMargin 10
|
|
|
|
#define kCloseButtonTitle "\pClose"
|
|
#define kCloseButtonWidth 70
|
|
#define kCloseButtonHeight 20
|
|
#define kCloseButtonBottomMargin 12
|
|
|
|
/* movableDBoxProc gives a compact window with a short, draggable
|
|
title bar and no zoom/grow boxes - the closest built-in
|
|
approximation to a "palette" style window available without
|
|
shipping a custom WDEF resource, which a genuinely Finder-Get-Info-
|
|
style thin palette title bar would need. Always a plain NewWindow(),
|
|
never NewCWindow(), regardless of gHasColourQD: this window only
|
|
ever draws plain black text and one button, so there's nothing here
|
|
that benefits from being a colour window, and one less place to
|
|
carry that complexity. */
|
|
#define kInfoWindowProcID movableDBoxProc
|
|
|
|
#define kRefreshIntervalTicks 60 /* about once a second, while a render is active */
|
|
|
|
static WindowPtr gInfoWindow = NULL;
|
|
static ControlHandle gCloseButton = NULL;
|
|
static unsigned long gLastRefreshTick = 0;
|
|
static Boolean gWasRenderActiveLastCheck = false;
|
|
|
|
static void AppendPascalString(Str255 dst, ConstStr255Param src);
|
|
static void AppendCString(Str255 dst, const char *src);
|
|
static void AppendNumber(Str255 dst, long n);
|
|
static void DrawInfoLine(short lineNumber, ConstStr255Param text, short firstBaseline, short lineHeight);
|
|
static void BuildFractalLine(Str255 line);
|
|
static void BuildResolutionLine(Str255 line);
|
|
static void BuildDepthLine(Str255 line);
|
|
static void BuildTimeLine(Str255 line);
|
|
static void BuildZoomLine(Str255 line, const FractalParameters *params);
|
|
static void BuildMaxIterationsLine(Str255 line, const FractalParameters *params);
|
|
static void BuildConstantLine(Str255 line, const FractalParameters *params);
|
|
|
|
/* AppendPascalString()
|
|
Appends src onto dst, truncating rather than overflowing if dst is
|
|
already close to Str255's 255-byte cap - never actually reached by
|
|
anything this file builds, but cheap insurance regardless. */
|
|
static void AppendPascalString(Str255 dst, ConstStr255Param src) {
|
|
short spaceLeft = 255 - dst[0];
|
|
short copyLength = (src[0] < spaceLeft) ? src[0] : spaceLeft;
|
|
short i;
|
|
|
|
for (i = 1; i <= copyLength; i++)
|
|
dst[dst[0] + i] = src[i];
|
|
|
|
dst[0] += copyLength;
|
|
}
|
|
|
|
/* AppendCString()
|
|
Same as AppendPascalString(), but for a NUL-terminated C string -
|
|
what sprintf() produces, used below for the floating-point
|
|
parameter lines NumToString() can't format. */
|
|
static void AppendCString(Str255 dst, const char *src) {
|
|
short spaceLeft = 255 - dst[0];
|
|
short i = 0;
|
|
|
|
while (src[i] != '\0' && i < spaceLeft) {
|
|
dst[dst[0] + i + 1] = src[i];
|
|
i++;
|
|
}
|
|
|
|
dst[0] += i;
|
|
}
|
|
|
|
/* AppendNumber()
|
|
Appends the decimal representation of a whole number onto dst. */
|
|
static void AppendNumber(Str255 dst, long n) {
|
|
Str255 numberString;
|
|
|
|
NumToString(n, numberString);
|
|
AppendPascalString(dst, numberString);
|
|
}
|
|
|
|
/* DrawInfoLine()
|
|
Draws one line of text at a vertical position determined only by
|
|
lineNumber (0-based), firstBaseline (where line 0's baseline sits),
|
|
and lineHeight (the current font's real line spacing - see
|
|
DrawInfoWindowContent()) - so each Build*Line() function can be
|
|
drawn independently without tracking a running position itself. */
|
|
static void DrawInfoLine(short lineNumber, ConstStr255Param text, short firstBaseline, short lineHeight) {
|
|
MoveTo(kLeftMargin, firstBaseline + lineNumber * lineHeight);
|
|
DrawString(text);
|
|
}
|
|
|
|
/* BuildFractalLine()
|
|
"Fractal: <name>", or "(none selected)" via CurrentFractalName()
|
|
before anything's been chosen from the Fractal menu yet. */
|
|
static void BuildFractalLine(Str255 line) {
|
|
line[0] = 0;
|
|
AppendPascalString(line, "\pFractal: ");
|
|
AppendPascalString(line, CurrentFractalName());
|
|
}
|
|
|
|
/* BuildResolutionLine()
|
|
"Resolution: <width> x <height>". */
|
|
static void BuildResolutionLine(Str255 line) {
|
|
short imageWidth, imageHeight;
|
|
|
|
GetFractalResolution(&imageWidth, &imageHeight);
|
|
|
|
line[0] = 0;
|
|
AppendPascalString(line, "\pResolution: ");
|
|
AppendNumber(line, imageWidth);
|
|
AppendPascalString(line, "\p x ");
|
|
AppendNumber(line, imageHeight);
|
|
}
|
|
|
|
/* BuildDepthLine()
|
|
"Screen depth: <n>-bit" - the real screen's current depth (see
|
|
CurrentScreenDepth() in mwWindow.c), which is what actually decides
|
|
whether the fractal renders in colour or, at 1-bit/2-bit, in the
|
|
same dither patterns as a genuine black-and-white Mac. */
|
|
static void BuildDepthLine(Str255 line) {
|
|
line[0] = 0;
|
|
AppendPascalString(line, "\pScreen depth: ");
|
|
AppendNumber(line, CurrentScreenDepth());
|
|
AppendPascalString(line, "\p-bit");
|
|
}
|
|
|
|
/* BuildTimeLine()
|
|
"Rendering... <n> sec elapsed" while a render is active, or
|
|
"Render time: <n> sec" once it's stopped - whether that's by
|
|
finishing normally or being aborted; RenderElapsedTicks() doesn't
|
|
distinguish those two, and neither does this line. */
|
|
static void BuildTimeLine(Str255 line) {
|
|
unsigned long elapsedSeconds = RenderElapsedTicks() / 60;
|
|
|
|
line[0] = 0;
|
|
|
|
if (IsRenderActive()) {
|
|
AppendPascalString(line, "\pRendering... ");
|
|
AppendNumber(line, elapsedSeconds);
|
|
AppendPascalString(line, "\p sec elapsed");
|
|
} else {
|
|
AppendPascalString(line, "\pRender time: ");
|
|
AppendNumber(line, elapsedSeconds);
|
|
AppendPascalString(line, "\p sec");
|
|
}
|
|
}
|
|
|
|
/* BuildZoomLine()/BuildMaxIterationsLine()/BuildConstantLine()
|
|
The maths behind the current fractal - see the FractalParameters
|
|
comment in mwWindow.h for which of these apply to which fractal.
|
|
Zoom applies to any type with a view (FractalTypeHasView(), mwWindow.h -
|
|
Lyapunov included, even though it isn't escape-time); Max Iterations
|
|
and the constant line are escape-time-specific (Julia's own constant
|
|
only, so far). NumToString() only formats whole numbers, so the
|
|
floating-point values go through sprintf() (this project already
|
|
links its ANSI library) and AppendCString() instead. */
|
|
static void BuildZoomLine(Str255 line, const FractalParameters *params) {
|
|
char buffer[32];
|
|
|
|
line[0] = 0;
|
|
AppendPascalString(line, "\pZoom: ");
|
|
sprintf(buffer, "%.4f", params->zoom);
|
|
AppendCString(line, buffer);
|
|
}
|
|
|
|
static void BuildMaxIterationsLine(Str255 line, const FractalParameters *params) {
|
|
line[0] = 0;
|
|
AppendPascalString(line, "\pMax iterations: ");
|
|
AppendNumber(line, params->maxIterations);
|
|
}
|
|
|
|
static void BuildConstantLine(Str255 line, const FractalParameters *params) {
|
|
char buffer[32];
|
|
|
|
line[0] = 0;
|
|
AppendPascalString(line, "\pc = ");
|
|
sprintf(buffer, "%.5f", params->constantRe);
|
|
AppendCString(line, buffer);
|
|
AppendPascalString(line, (params->constantIm >= 0.0) ? "\p + " : "\p - ");
|
|
sprintf(buffer, "%.5f", fabs(params->constantIm));
|
|
AppendCString(line, buffer);
|
|
AppendPascalString(line, "\pi");
|
|
}
|
|
|
|
/* IsInfoWindow()/CloseInfoWindow()
|
|
Let MandyWindow.c's event dispatch recognise and dismiss this
|
|
window without needing gInfoWindow exposed directly - it stays
|
|
private to this file, matching how mwWindow.c keeps mwWindow
|
|
itself as the only thing anyone else needs a direct handle to. */
|
|
Boolean IsInfoWindow(WindowPtr w) {
|
|
return (w != NULL) && (w == gInfoWindow);
|
|
}
|
|
|
|
void CloseInfoWindow(void) {
|
|
if (gInfoWindow != NULL)
|
|
HideWindow(gInfoWindow);
|
|
}
|
|
|
|
/* ShowInfoWindow()
|
|
Creates the info window and its Close button the first time it's
|
|
needed, or just brings an already-created one forward and up to
|
|
date on later uses - matching how "Open" on the main fractal window
|
|
already behaves (see mwMenus.c's fileID case). The button is
|
|
created once and simply hidden along with the rest of the window
|
|
thereafter (CloseInfoWindow() hides rather than disposes), so
|
|
there's nothing further to set up on repeat uses. */
|
|
void ShowInfoWindow(void) {
|
|
if (gInfoWindow == NULL) {
|
|
Rect bounds, buttonRect;
|
|
|
|
SetRect(&bounds, kInfoWindowLeft, kInfoWindowTop,
|
|
kInfoWindowLeft + kInfoWindowWidth, kInfoWindowTop + kInfoWindowHeight);
|
|
gInfoWindow = NewWindow(0L, &bounds, kInfoWindowTitle, true,
|
|
kInfoWindowProcID, (WindowPtr) -1L, true, 0);
|
|
|
|
SetRect(&buttonRect,
|
|
(kInfoWindowWidth - kCloseButtonWidth) / 2,
|
|
kInfoWindowHeight - kCloseButtonHeight - kCloseButtonBottomMargin,
|
|
(kInfoWindowWidth - kCloseButtonWidth) / 2 + kCloseButtonWidth,
|
|
kInfoWindowHeight - kCloseButtonBottomMargin);
|
|
gCloseButton = NewControl(gInfoWindow, &buttonRect, kCloseButtonTitle,
|
|
true, 0, 0, 0, pushButProc, 0L);
|
|
} else {
|
|
ShowWindow(gInfoWindow);
|
|
}
|
|
|
|
SelectWindow(gInfoWindow);
|
|
gLastRefreshTick = TickCount();
|
|
}
|
|
|
|
/* DrawInfoWindowContent()
|
|
Paints every line that applies to the current fractal, then the
|
|
Close button on top. Called from MandyWindow.c's updateEvt case,
|
|
between that BeginUpdate()/EndUpdate() pair, exactly like
|
|
DrawContent() is for the main window.
|
|
|
|
The 9pt font is set here (rather than once at window creation)
|
|
because font/size are per-port state, and re-asserting it on every
|
|
redraw costs nothing but guarantees it's never accidentally left in
|
|
whatever a previous draw call set the port to. Line spacing comes
|
|
from GetFontInfo() against that same 9pt setting rather than a
|
|
guessed constant, so it stays correct if the font size here ever
|
|
changes again. */
|
|
void DrawInfoWindowContent(void) {
|
|
Str255 line;
|
|
FontInfo fontInfo;
|
|
short lineNumber = 0;
|
|
short lineHeight, firstBaseline;
|
|
FractalParameters params;
|
|
|
|
if (gInfoWindow == NULL)
|
|
return;
|
|
|
|
SetPort(gInfoWindow);
|
|
EraseRect(&gInfoWindow->portRect);
|
|
|
|
TextFont(applFont);
|
|
TextSize(kInfoFontSize);
|
|
GetFontInfo(&fontInfo);
|
|
lineHeight = fontInfo.ascent + fontInfo.descent + fontInfo.leading;
|
|
firstBaseline = kTopMargin + fontInfo.ascent;
|
|
|
|
BuildFractalLine(line);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
|
|
BuildResolutionLine(line);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
|
|
BuildDepthLine(line);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
|
|
BuildTimeLine(line);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
|
|
params = GetFractalParameters();
|
|
|
|
if (FractalTypeHasView(width)) {
|
|
BuildZoomLine(line, ¶ms);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
}
|
|
|
|
{
|
|
FractalFamily family = FractalFamilyForWidth(width);
|
|
if (family == kFractalFamilyEscapeTime || family == kFractalFamilyConvergence) {
|
|
BuildMaxIterationsLine(line, ¶ms);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
}
|
|
}
|
|
|
|
if (FractalTypeHasFixedConstant(width)) {
|
|
BuildConstantLine(line, ¶ms);
|
|
DrawInfoLine(lineNumber++, line, firstBaseline, lineHeight);
|
|
}
|
|
|
|
DrawControls(gInfoWindow);
|
|
}
|
|
|
|
/* HandleInfoWindowClick()
|
|
Called from MandyWindow.c's HandleMouseDown() when a content click
|
|
lands in this window (and it's already frontmost). where is in
|
|
global coordinates, as EventRecord.where always is. */
|
|
void HandleInfoWindowClick(Point where) {
|
|
ControlHandle clickedControl;
|
|
short part;
|
|
|
|
if (gInfoWindow == NULL)
|
|
return;
|
|
|
|
SetPort(gInfoWindow);
|
|
GlobalToLocal(&where);
|
|
|
|
part = FindControl(where, gInfoWindow, &clickedControl);
|
|
|
|
if (part != 0 && clickedControl == gCloseButton) {
|
|
if (TrackControl(clickedControl, where, NULL) != 0)
|
|
CloseInfoWindow();
|
|
}
|
|
}
|
|
|
|
/* RefreshInfoWindowIfNeeded()
|
|
Called from HandleEvent()'s idle branch alongside
|
|
AdvanceFractalRender(), so the elapsed-time line keeps counting up
|
|
while a render is active. Refreshes roughly once a second while
|
|
active, then exactly once more right when a render stops - to show
|
|
its final time - and stays quiet after that, since nothing it
|
|
displays changes again until a new render starts. Does nothing if
|
|
the window doesn't exist or isn't currently visible. */
|
|
void RefreshInfoWindowIfNeeded(void) {
|
|
unsigned long now = TickCount();
|
|
Boolean isActive = IsRenderActive();
|
|
Boolean shouldRefresh;
|
|
GrafPtr savedPort;
|
|
|
|
if (gInfoWindow == NULL || !((WindowPeek) gInfoWindow)->visible)
|
|
return;
|
|
|
|
shouldRefresh = isActive
|
|
? ((now - gLastRefreshTick) >= kRefreshIntervalTicks)
|
|
: gWasRenderActiveLastCheck;
|
|
|
|
gWasRenderActiveLastCheck = isActive;
|
|
|
|
if (!shouldRefresh)
|
|
return;
|
|
|
|
gLastRefreshTick = now;
|
|
|
|
GetPort(&savedPort);
|
|
SetPort(gInfoWindow);
|
|
InvalRect(&gInfoWindow->portRect);
|
|
SetPort(savedPort);
|
|
}
|