mgsLib  1.3
Mermaja's Graphic Screen. A simple C library to build Windows graphic applications from console programs.
lines.c

This example shows how to implement a simple user interface using the advanced mouse functions. At the same time it is also a good example of a simple yet aesthetic animation.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// mgsLib header file
#include "mgsLib.h"
// Constants
#define SCRW 800 // Screen width
#define SCRH 600 // and height
#define SCRTITLE "Drawing with lines"
#define SCRBKGC MGS_CBLACK
#define RMAX ((SCRH / 2) - 10) // Max radius
#define RMIN (SCRH / 8) // Min radius
#define LMIN 5 // Min number of sides
#define LMAX 20 // Max number of sides
#define LINIT 9 // Initial number of sides
#define CCOLOR MGS_CBLUE // Some colors
#define CSCOLOR MGS_CGREY
#define PCOLOR MGS_CCYAN
#define RMARGEN 400 // Circumference click band
#define RWIDTH 100 // Conf squares data
#define RHEIGHT 80
#define RCTMAR 20
#define RCOLOR 0xE02020
#define LSHADOW 6 // Distance to the shadow of lines
#define CSHADOW 0x282828 // Its color
#define NUMSTARS 600 // Stars for the background
#define CHKSIZE 10 // Little check squares size
#define CHKX1 10 // and positions
#define CHKX2 (SCRW - 135)
#define CHKY1 30
#define CHKY2 (SCRH - 30)
#define EF1LIMIT 20 // Count limit for the wave efect
#define EF2RISE 90 // Steps for the pulse to rise
#define EF2FALL 45 // and to fall
#define EF2EXTRA 0.05 // % of radius to rise
// Two states: configuration and animation
enum states {CONFIG, DRAW};
int main()
{
// General variables
int i, j, dist, r2, mx, my, scr, dumb, nlados = LINIT;
double *puntos, ang, vang, step, radio;
// Variables for the configuration UI
int rectX[3], rectY[3];
char *texts[3] = {"Click to decrease side count", "Click to increase side count", "Click to show animation"};
// Variables for the special effects
int ef1front, ef1vertex, ef1count, ef1O, ef1D;
int ef1Orig[5], ef1Dest[5], ef1Colors[5] = {0x55FF00, 0xAAFF00, MGS_CCYAN, 0xAAFF00, 0x55FF00};
int ef2idx;
double ef2Rad[EF2RISE + EF2FALL];
// Flags
unsigned char msgRad, msgRadMin, msgRadMax, msgClick[3], modif, press, shadow, anti, efcol1, efcol2, efcol2Sync;
// The first state is configuration
enum states st = CONFIG;
// Create the screen
if ((scr = mgsCreateScreen(SCRW, SCRH, SCRTITLE, SCRBKGC, NULL)) < 0) {
mgsShowError(scr, "Error creating screen. Aborting program", MGS_EFWAIT);
exit(EXIT_FAILURE);
}
// And a small font for the messages
mgsSetFont(scr, 24, 0xFF8020, MGS_TRANSPARENT);
// Init the interface boxes
rectX[0] = RCTMAR;
rectY[0] = RCTMAR;
rectX[1] = SCRW - RCTMAR - RWIDTH;
rectY[1] = RCTMAR;
rectX[2] = RCTMAR;
rectY[2] = SCRH - RCTMAR - RHEIGHT;
// Set the radius to an average between max and min
radio = (RMAX + RMIN) >> 1;
// and the radius modification flag to no
modif = 0;
// And chose a suitable animation speed
while(1) {
// We use the mouse frequently, so update its data every frame
mx = mgsMouseX(scr);
my = mgsMouseY(scr);
// And keep generating random numbers to let the sequence run
dumb += rand();
switch(st) {
// Config state: change the radius and number of sides using the mouse
case CONFIG:
// Set all five message flags to no
msgRadMax = 0;
msgRadMin = 0;
for (i = 0; i < 3; msgClick[i++] = 0);
// Calculate the square of cursor distance to the center of the screen
dist = (mx - (SCRW >> 1)) * (mx - (SCRW >> 1)) + (my - (SCRH >> 1)) * (my - (SCRH >> 1));
// and the square of the current radius
r2 = radio * radio;
// If they are as close as RMARGEN, the mouse cursor is over the circumference, so set the flag to yes
msgRad = ((dist < r2 + RMARGEN) && (dist > r2 - RMARGEN)) ? 1 : 0;
// Detect how the user is pressing the left mouse button
press = mgsLeftMousePress(scr, 0, 0, SCRW, SCRH);
// If the radius is not being modified
if (!modif) {
// and it just pressed at the detection margin
// set modifying to yes but do not show any message
if ((press & MGS_MNEW) && msgRad) {
msgRad = 0;
modif = 1;
}
}
// If modifying do not show message. If the button is released
// reset the modifying flag, else track te cursor between max
// and min radius, activating the messages if reached.
else {
msgRad = 0;
if (press == MGS_MNO)
modif = 0;
else {
radio = sqrt(dist);
if (radio > RMAX) {
radio = RMAX;
msgRadMax = 1;
}
else if (radio < RMIN) {
radio = RMIN;
msgRadMin = 1;
}
}
}
// If the left mouse is not pressed, check if the user has just clicked on any rectangle
if (press == MGS_MNO) {
// To decrease the number of sides
if (mgsLeftMouseClk(scr, rectX[0], rectY[0], rectX[0] + RWIDTH, rectY[0]+ RHEIGHT)) {
if (nlados > LMIN) nlados--;
}
// To increase it
else if (mgsLeftMouseClk(scr, rectX[1], rectY[1], rectX[1] + RWIDTH, rectY[1]+ RHEIGHT)) {
if (nlados < LMAX) nlados++;
}
// or to end configuration and go to animation
else if (mgsLeftMouseClk(scr, rectX[2], rectY[2], rectX[2] + RWIDTH, rectY[2]+ RHEIGHT)) {
// Change the state
st = DRAW;
// Create the array to hold the x, y coordinates of each vertex
puntos = malloc (2 * nlados * sizeof(double));
if (puntos == NULL) {
puts("Memory failure");
exit(EXIT_SUCCESS);
}
// Set the rotated angle
ang = 0.0;
// the step between consecutive vertices
step = 2.0 * M_PI / nlados;
// Effect flags to 0
// shadow effect
shadow = 0;
// counterclockwise flag
anti = 0;
// And the initial values:
efcol1 = 0;
// ef1 is wave, so set the initial destination point index
ef1front = nlados - 4;
// the initial starting point index
ef1vertex = 0;
// and the delay counter
ef1count = EF1LIMIT - 1;
efcol2 = 0;
efcol2Sync = 0;
// ef2 is pulse, so set the initial pulsed radius index to 0
ef2idx = 0;
// and fill the array with the rising steps EF2RISE from the normal radius to the last increased an EF2ETRA percentaje
ef2Rad[0] = radio;
for (i = 1; i < EF2RISE; i++)
ef2Rad[i] = ef2Rad[i - 1] + radio * EF2EXTRA / EF2RISE;
// then fall EF2FALL steps to the original radius
for (; i < EF2FALL + EF2RISE; i++)
ef2Rad[i] = ef2Rad[i - 1] - radio * EF2EXTRA / EF2FALL;
// prepare the background, clear the screen
// and generate the stars to set the new background
for (i = 0; i < NUMSTARS; i++)
mgsEllipse(scr, rand() % SCRW, rand() % SCRH, rand() % 3, rand() % 3, mgsRGBColor(rand() % 255, rand() % 255, rand() % 255));
break;
}
// Prepare the message flags if the mouse pointer is over any rectangle
else if (mgsMouseOver(scr, rectX[0], rectY[0], rectX[0] + RWIDTH, rectY[0]+ RHEIGHT)) {
msgClick[0] = 1;
}
else if (mgsMouseOver(scr, rectX[1], rectY[1], rectX[1] + RWIDTH, rectY[1]+ RHEIGHT)) {
msgClick[1] = 1;
}
else if (mgsMouseOver(scr, rectX[2], rectY[2], rectX[2] + RWIDTH, rectY[2]+ RHEIGHT)) {
msgClick[2] = 1;
}
}
// Clear the screen and draw the new objects
// The rectangles near three corners
for (i = 0; i < 3; i++)
mgsRectangle(scr, rectX[i], rectY[i], rectX[i] + RWIDTH, rectY[i]+ RHEIGHT, RCOLOR);
// The actual circumference and the limiting ones
mgsDrawEllipse(scr, SCRW >> 1, SCRH >> 1, RMAX, RMAX, 2, CSCOLOR);
mgsDrawEllipse(scr, SCRW >> 1, SCRH >> 1, RMIN, RMIN, 2, CSCOLOR);
mgsDrawEllipse(scr, SCRW >> 1, SCRH >> 1, radio, radio, 4, CCOLOR);
// The polygon with its changing number of sides
mgsDrawRegPolygonCR(scr, nlados, SCRW >> 1, SCRH >> 1, radio, 2, PCOLOR);
// Then draw the UI messages if any is active. Only one can be so
if (msgRad) mgsPuts(scr, mx + 2, my - 25, "Click and drag to change radius");
else if (msgRadMax) mgsPuts(scr, mx + 2, my - 25, "It cannot be bigger");
else if (msgRadMin) mgsPuts(scr, mx + 2, my - 25, "It cannot be smaller");
else if (msgClick[0]) mgsPuts(scr, mx + 2, my - 25, texts[0]);
else if (msgClick[1]) mgsPuts(scr, mx - 290, my - 25, texts[1]);
else if (msgClick[2]) mgsPuts(scr, mx + 2, my - 25, texts[2]);
break;
case DRAW:
// Animation state. Drawing lines from each vertex to those not contiguous, and have all the whole rotating.
// The radius varies accordingly to the pulse effect, so calculate the instant radius first, and prepare its
// value for the next frame using ef2idx
if (efcol2Sync) {
radio = ef2Rad[ef2idx];
ef2idx++;
if (ef2idx == EF2RISE + EF2FALL) {
efcol2Sync = efcol2;
ef2idx = 0;
}
}
else radio = ef2Rad[0];
// Now update the drawing angle according to the rotation direction, and use vang to calculate
// all the vertices
vang = ang;
ang += anti ? -0.002 : 0.002;
for(i = 0; i < nlados; i++) {
vang += step;
puntos[2 * i] = radio * cos(vang) + (SCRW >> 1);
puntos[2 * i + 1] = radio * sin(vang) + (SCRH >> 1);
}
// Verifiy if the user clicks on any box to enable or disable effects
if (mgsLeftMouseClk(scr, CHKX1, CHKY1, CHKX1 + CHKSIZE, CHKY1 + CHKSIZE))
shadow = 1 - shadow;
else if (mgsLeftMouseClk(scr, CHKX1, CHKY2, CHKX1 + CHKSIZE, CHKY2 + CHKSIZE))
efcol1 = 1 - efcol1;
else if (mgsLeftMouseClk(scr, CHKX2, CHKY1, CHKX2 + CHKSIZE, CHKY1 + CHKSIZE)) {
efcol2 = 1 - efcol2;
// With efcol2Sync the pulse effect always completes its cycle, be it deactivated in any moment
if (efcol2 == 1) efcol2Sync = 1;
}
else if (mgsLeftMouseClk(scr, CHKX2, CHKY2, CHKX2 + CHKSIZE, CHKY2 + CHKSIZE))
anti = 1 - anti;
// Calculate the highlighted lines for the wave effect
if (efcol1) {
// The first origin and destination indexes are kept in ef1vertex and ef1front
ef1O = ef1vertex;
ef1D = ef1front;
// We calculate 5 origin and destination points (its index). If the destination
// reaches the last line from the origin point, change the origin and
// and reset the corresponding destination to the first.
for (i = 0; i < 5; i++) {
ef1Orig[i] = ef1O;
ef1Dest[i] = (ef1O + 2 + ef1D) % nlados;
ef1D--;
if (ef1D < 0) {
ef1D = nlados - 4;
ef1O = (ef1O + 1) % nlados;
}
}
// If the counter reaches the frame limit increase
// the first destination point. If it reaches to the
// last one, increase the origin point in the oposite
// direction. Also reset the counter.
ef1count++;
if (ef1count == EF1LIMIT) {
ef1count = 0;
ef1front--;
if (ef1front < 0) {
ef1front = nlados - 4;
ef1vertex = (ef1vertex + 1) % nlados;
}
}
}
// If the user press S end the program
if (mgsKeyPressed('S')) {
puts("GOOD BYE");
exit(EXIT_SUCCESS);
}
// Draw the current objects, first clear the screen
// Set the font and print the exit message
mgsSetFont(scr, 40, 0x804040, MGS_TRANSPARENT);
mgsPuts(scr, (SCRW >> 1) - 145, 10, "PRESS S TO EXIT");
// Set the font for the effect texts
mgsSetFont(scr, 24, 0xFF8020, MGS_TRANSPARENT);
// Draw the shadow if selected.
if (shadow) {
// First the shadows of the vertice, from ech vertex to a point shifted LSHADOW in x and y
for (j = 0; j < nlados; j++)
mgsLine(scr, puntos[2 * j], puntos[2 * j + 1], puntos[2 * j] + LSHADOW, puntos[2 * j + 1] + LSHADOW, 2, CSHADOW);
// Then the same lines as the main figure, but shifted LSHADOW in each of its coordinates.
for (j = 2; j < nlados - 1; j++)
mgsLine(scr, puntos[0] + LSHADOW, puntos[1] + LSHADOW, puntos[2 * j] + LSHADOW, puntos[2 * j + 1] + LSHADOW, 2, CSHADOW);
for(i = 1; i < nlados - 2; i++)
for (j = i + 2; j < nlados; j++)
mgsLine(scr, puntos[2 * i] + LSHADOW, puntos[2 * i + 1] + LSHADOW, puntos[2 * j] + LSHADOW, puntos[2 * j + 1] + LSHADOW, 2, CSHADOW);
}
// Draw the main figure. Just al algorithm to draw lines from each vertex to all non adjacet to it.
for (j = 2; j < nlados - 1; j++)
mgsLine(scr, puntos[0], puntos[1], puntos[2 * j], puntos[2 * j + 1], 2, MGS_CGREEN);
for(i = 1; i < nlados - 2; i++)
for (j = i + 2; j < nlados; j++)
mgsLine(scr, puntos[2 * i], puntos[2 * i + 1], puntos[2 * j], puntos[2 * j + 1], 2, MGS_CGREEN);
mgsLine(scr, puntos[0], puntos[1], puntos[2 * nlados - 2], puntos[2 * nlados - 1], 1, MGS_CGREY);
// Outline the polygon sides
for (j = 0; j < nlados - 1; j++)
mgsLine(scr, puntos[2 * j], puntos[2 * j + 1], puntos[2 * j + 2], puntos[2 * j + 3], 1, MGS_CGREY);
// If the wave effect is active, draw 5 lines using the calculated indexes, and fixed colors
if (efcol1)
for (i = 0; i < 5; i++)
mgsLine(scr, puntos[2 * ef1Orig[i]], puntos[2 * ef1Orig[i] + 1], puntos[2 * ef1Dest[i]], puntos[2 * ef1Dest[i] + 1], 3, ef1Colors[i]);
// Draw red circles on the vertices
for (j = 0; j < nlados; j++)
mgsEllipse(scr, puntos[2 * j], puntos[2 * j + 1], 4, 4, MGS_CRED);
// Draw the effect squares and its texts
mgsRectangle(scr, CHKX1, CHKY1, CHKX1 + CHKSIZE, CHKY1 + CHKSIZE, MGS_CCYAN);
mgsPuts(scr, CHKX1 + CHKSIZE + 10, CHKY1 - 7, "Shadow");
mgsRectangle(scr, CHKX1, CHKY2, CHKX1 + CHKSIZE, CHKY2 + CHKSIZE, MGS_CCYAN);
mgsPuts(scr, CHKX1 + CHKSIZE + 10, CHKY2 - 7, "Wave");
mgsRectangle(scr, CHKX2, CHKY1, CHKX2 + CHKSIZE, CHKY1 + CHKSIZE, MGS_CCYAN);
mgsPuts(scr, CHKX2 + CHKSIZE + 10, CHKY1 - 7, "Pulse");
mgsRectangle(scr, CHKX2, CHKY2, CHKX2 + CHKSIZE, CHKY2 + CHKSIZE, MGS_CCYAN);
mgsPuts(scr, CHKX2 + CHKSIZE + 10, CHKY2 - 7, "CClkWise");
// And ellipses on the selected effects
if (shadow) mgsEllipse(scr, CHKX1 + (CHKSIZE >> 1), CHKY1 + (CHKSIZE >> 1), CHKSIZE >> 1, CHKSIZE >> 1, MGS_CBLUE);
if (efcol1) mgsEllipse(scr, CHKX1 + (CHKSIZE >> 1), CHKY2 + (CHKSIZE >> 1), CHKSIZE >> 1, CHKSIZE >> 1, MGS_CBLUE);
if (efcol2) mgsEllipse(scr, CHKX2 + (CHKSIZE >> 1), CHKY1 + (CHKSIZE >> 1), CHKSIZE >> 1, CHKSIZE >> 1, MGS_CBLUE);
if (anti) mgsEllipse(scr, CHKX2 + (CHKSIZE >> 1), CHKY2 + (CHKSIZE >> 1), CHKSIZE >> 1, CHKSIZE >> 1, MGS_CBLUE);
break;
}
// Update the screen and synchronize to frame time
}
}