Blob Blame Raw

#include <helianthus.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>


#define MAXREC       1000
#define MARGINX      30.0
#define MARGINY      15.0
#define MARGINLINE0  30.0
#define MARGINLINE1  40.0
#define BASELINE     22.0
#define PADDING      15.0
#define SEPARATOR    15.0
#define TEXTWIDTH   300.0


typedef struct {
  char text[128];
  double w;
  double h;
} Date;

typedef struct {
  char text[1024];
  double w;
  double h;
} Text;

typedef struct {
  char text[128];
  int index;
  int begin;
  int end;
  double w;
  double y0;
  double y1;
} Part;

typedef struct {
  double x, w;
} Column;


typedef struct {
  Date date;
  Text text;
  Part part;
} BaseRecord;

typedef struct {
  Date *date;
  Text *text;
  Part *part;
  double y;
} Record;



BaseRecord recordsSrc[MAXREC];
Record records[MAXREC];
Part *parts[MAXREC];
Date *dates[MAXREC];
Column columns[MAXREC];
int partCount;
int dateCount;
int recCount;
int colCount;
double datesWidth;
double fullWidth, fullHeight;

double dx, dy;
double mx, my;
double zm = 1;

int curRec;
double curFade;



void updateLayout();


double calcWidth(char *b, char *e) {
  char c = *e;
  *e = 0;
  TextLayout tl = createTextLayout(b);
  *e = c;
  double w = textLayoutGetWidth(tl);
  textLayoutDestroy(tl);
  return w;
}

void wordWrap(char *buf, double maxWidth) {
  int len = strlen(buf);
  if (calcWidth(buf, buf + len) <= maxWidth) return;

  char *last = NULL;
  for(char *p = buf + len - 1; p > buf; --p) {
    if (isspace(*p)) {
      last = p;
      if (calcWidth(buf, p) < maxWidth)
        { *p = '\n'; wordWrap(p+1, maxWidth); return; }
    }
  }
  if (!last) return;

  *last = '\n';
  wordWrap(last+1, maxWidth);
}


void load(const char *filename) {
  curRec = 0;
  curFade = 0;
  recCount = 0;
  partCount = 0;
  dateCount = 0;
  colCount = 0;
  datesWidth = 0;
  memset(recordsSrc, 0, sizeof(recordsSrc));
  memset(records, 0, sizeof(records));
  memset(parts, 0, sizeof(parts));
  memset(dates, 0, sizeof(dates));
  memset(columns, 0, sizeof(columns));

  FILE *f = fopen(filename, "r");
  if (!f) {
    printf("cannot open file for read: %s\n", filename);
    return;
  }

  int mode = 0;
  BaseRecord *rs = recordsSrc;
  char *p = rs->date.text;
  char *e = p + sizeof(rs->date.text);
  while(1) {
    int c = fgetc(f);
    if (c <= 0 || c >= 256) break;
    if (c == '\t') {
      if (mode == 0) {
        mode = 1; p = rs->text.text; e = p + sizeof(rs->text.text);
      } else
      if (mode == 1) {
        mode = 2; p = rs->part.text; e = p + sizeof(rs->part.text);
      } else
      if (mode == 2) {
        mode = 3;
      }
    } else
    if (c == '\n') {
        if (rs->date.text[0] || rs->text.text[0] || rs->part.text[0]) {
          ++recCount; ++rs;
          if (recCount >= MAXREC) break;
        }
        mode = 0; p = rs->date.text; e = p + sizeof(rs->date.text);
    } else
    if (c != '\r' && p+1 < e) {
      *p++ = c;
    }
  }
  fclose(f);

  if (recCount < MAXREC)
    if (rs->date.text[0] || rs->text.text[0] || rs->part.text[0])
      ++recCount;

  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    BaseRecord *rs = &recordsSrc[i];
    wordWrap(rs->text.text, TEXTWIDTH);

    for(int j = 0; j <= i; ++j) {
      BaseRecord *rrs = &recordsSrc[j];
      if (!r->date && !strcmp(rs->date.text, rrs->date.text)) r->date = &rrs->date;
      if (!r->text && !strcmp(rs->text.text, rrs->text.text)) r->text = &rrs->text;
      if (!r->part && !strcmp(rs->part.text, rrs->part.text)) r->part = &rrs->part;
    }
    if (r->part == &rs->part)
      parts[partCount++] = r->part;
    if (r->date == &rs->date)
      dates[dateCount++] = r->date;

    TextLayout tl = createTextLayout(r->text->text);
    r->text->w = textLayoutGetWidth(tl);
    r->text->h = textLayoutGetHeight(tl);
    textLayoutDestroy(tl);

    tl = createTextLayout(r->date->text);
    r->date->w = textLayoutGetWidth(tl);
    r->date->h = textLayoutGetHeight(tl);
    textLayoutDestroy(tl);

    if (r->part->w < r->text->w)
      r->part->w = r->text->w;
    if (datesWidth < r->date->w)
      datesWidth = r->date->w;
  }

  updateLayout();
}


void writeUnwrapped(const char *buf, FILE *f) {
  while(*buf) {
    fputc(*buf == '\n' ? ' ' : *buf, f);
    ++buf;
  }
}


void save(const char *filename) {
  FILE *f = fopen(filename, "w");
  if (!f) {
    printf("cannot open file for write: %s\n", filename);
    return;
  }

  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    writeUnwrapped(r->date->text, f); fputc('\t', f);
    writeUnwrapped(r->text->text, f); fputc('\t', f);
    writeUnwrapped(r->part->text, f); fputc('\n', f);
  }

  fclose(f);
  printf("saved into: %s\n", filename);
}


void updateLayout() {
  colCount = 0;
  for(int i = 0; i < partCount; ++i) {
    parts[i]->index = -1;
    columns[i].w = 0;
  }

  double y = MARGINY + PADDING;
  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    Part *p = r->part;
    if (p->index < 0) {
      int idx = 0;
      while(p->text[idx] == '~') ++idx;
      while(1) {
        int found = TRUE;
        for(int j = i+1; j < recCount; ++j)
          if (records[j].part->index == idx)
            found = FALSE;
        if (found) break;
        ++idx;
      }
      p->index = idx;
      p->begin = i;
      p->y0 = y;

      if (colCount <= idx) colCount = idx + 1;
      if (columns[idx].w < p->w) columns[idx].w = p->w;
    }

    if (i > 0) {
      if (p == records[i-1].part) {
        y += SEPARATOR;
      } else {
        for(int j = i-1; j >= 0; --j) {
          if (records[j].part->index == p->index) {
            double py = records[j].part->y1 + 2*PADDING + MARGINY;
            if (y < py) y = py;
          }
        }
      }
    }

    r->y = y;
    p->end = i + 1;
    if (p->begin == i) p->y0 = r->y;
    p->y1 = y + r->text->h;

    y = p->y1;
  }

  double x = MARGINX + PADDING + datesWidth + PADDING + MARGINLINE0 + MARGINLINE1 + MARGINX;
  for(int i = 0; i < colCount; ++i) {
    columns[i].x = x;
    x += columns[i].w + 2*PADDING + MARGINX;
  }
  fullWidth = x;
  fullHeight = y + PADDING + MARGINY;
}


void drawRect(double x, double y, double w, double h) {
  noFill();
  stroke(COLOR_WHITE);
  strokeWidth(4);
  rectRounded( x - PADDING, y - PADDING, w + 2*PADDING, h + 2*PADDING, PADDING );
  fill(colorByRGBA(1, 1, 1, 0.75));
  stroke(COLOR_BLUE);
  strokeWidth(2);
  rectRounded( x - PADDING, y - PADDING, w + 2*PADDING, h + 2*PADDING, PADDING );
}


void drawRecords() {
  saveState();

  strokeWidth(2);
  stroke(COLOR_BLUE);

  double mr = PADDING/4.0;

  // lines
  double xl = MARGINX + PADDING + datesWidth + PADDING + MARGINLINE0;
  double xd = xl - MARGINLINE0;
  double h = 0;
  if (recCount > 0)
    h = records[recCount-1].y + records[recCount-1].text->h + MARGINY + PADDING;
  line(xl, 0, xl, h);
  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    double x = columns[ r->part->index ].x;
    double y = r->y + BASELINE;
    line(xd, y, x, y);
  }

  // text rects
  for(int i = 0; i < partCount; ++i) {
    Part *p = parts[i];
    Column *c = &columns[p->index];
    drawRect(c->x + PADDING, p->y0, p->w, p->y1 - p->y0);
  }

  // circles
  fill(COLOR_WHITE);
  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    double x = columns[ r->part->index ].x;
    double y = r->y + BASELINE;
    double mrr = i == curRec ? mr + curFade*mr : mr;
    circle(xl, y, mrr);
    circle(x, y, mrr);
  }

  // texts
  noFill();
  stroke(COLOR_BLACK);
  for(int i = 0; i < recCount; ++i) {
    Record *r = &records[i];
    Part *p = r->part;
    Date *d = r->date;
    double x = columns[ p->index ].x + PADDING;
    double y = r->y;
    text(x, y, r->text->text);
    text(xd - PADDING - d->w, y, d->text);
  }

  restoreState();
}


void saveImage(const char *filename) {
  double s = 1;
  Framebuffer fb = createFramebuffer((int)(fullWidth*s) + 1, (int)(fullHeight*s) + 1);

  saveState();
  target(fb);
  clear();
  zoom(s);
  drawRecords();
  if (viewportSave(filename))
    printf("image saved into: %s\n", filename);
  restoreState();

  framebufferDestroy(fb);
}


void init() {
  if (fileExists("data/output/history/ancient-dates.txt"))
    load("data/output/history/ancient-dates.txt");
  else
    load("data/history/ancient-dates.txt");

  dx = -fullWidth/2;
  dy = -450;
}


void draw() {
  double w = windowGetWidth();
  double h = windowGetHeight();

  double pmx = mx, pmy = my;
  mx = mouseX(), my = mouseY();

  if (mouseDown("left")) {
    dx += (mx - pmx)/zm;
    dy += (my - pmy)/zm;
  } else
  if (mouseDown("right")) {
    zm *= pow(2.0, -(my - pmy)/500.0);
  }

  if (keyEventGetCount(KEYEVENT_KEY_DOWN))
    curFade = 1;

  if (keyWentDown("up") && curRec > 0) {
    if (keyDown("any ctrl")) {
      Record r;
      memcpy(&r, &records[curRec], sizeof(r));
      memcpy(&records[curRec], &records[curRec-1], sizeof(r));
      memcpy(&records[curRec-1], &r, sizeof(r));
      updateLayout();
    }
    --curRec;
  } else
  if (keyWentDown("down") && curRec < recCount-1) {
    if (keyDown("any ctrl")) {
      Record r;
      memcpy(&r, &records[curRec], sizeof(r));
      memcpy(&records[curRec], &records[curRec+1], sizeof(r));
      memcpy(&records[curRec+1], &r, sizeof(r));
      updateLayout();
    }
    ++curRec;
  } else
  if (keyWentDown("s")) {
    save("data/output/history/ancient-dates.txt");
  } else
  if (keyWentDown("i")) {
    curFade = 0;
    saveImage("data/output/history/ancient-dates.png");
  }

  saveState();
  translate(w/2, h/2);
  zoom(zm);
  translate(dx, dy);

  drawRecords();
  restoreState();

  curFade -= windowGetFrameTime()*2;
  if (curFade < 0) curFade = 0;
}


int main() {
  windowSetResizable(TRUE);
  windowSetVariableFrameRate();
  windowSetInit(&init);
  windowSetDraw(&draw);
  windowRun();
  return 0;
}