summaryrefslogtreecommitdiff
path: root/opplysning-vis.c
diff options
context:
space:
mode:
Diffstat (limited to 'opplysning-vis.c')
-rw-r--r--opplysning-vis.c306
1 files changed, 306 insertions, 0 deletions
diff --git a/opplysning-vis.c b/opplysning-vis.c
new file mode 100644
index 0000000..ba4d9c9
--- /dev/null
+++ b/opplysning-vis.c
@@ -0,0 +1,306 @@
+#define _POSIX_C_SOURCE 199309L
+
+#include <err.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <libical/ical.h>
+#include <raylib.h>
+
+#include "sqlite/sqlite3.h"
+
+enum {
+ SCREEN_W = 1920,
+ SCREEN_H = 1080,
+};
+
+const char *maaned[] = {
+ "", "JANUAR", "FEBRUAR", "MARS", "APRIL", "MAI", "JUNI",
+ "JULI", "AUGUST", "SEPTEMBER", "OKTOBER", "NOVEMBER", "DESEMBER",
+};
+const char *ukedag[] = {
+ "SØNDAG", "MANDAG", "TIRSDAG", "ONSDAG", "TORSDAG", "FREDAG", "LØRDAG",
+};
+
+enum {
+ MAX_EVENTS = 64,
+ SIZEOF_TITLE = 60,
+};
+
+struct event {
+ time_t start;
+ time_t end;
+ int year;
+ int month;
+ int day;
+ int wday;
+ int hour;
+ int minute;
+ int end_hour;
+ int end_minute;
+ char title[SIZEOF_TITLE];
+};
+
+struct calendar {
+ char *database;
+ char *title;
+ char *subtitle;
+ size_t num_events;
+ struct event events[MAX_EVENTS];
+};
+
+void
+load_events(struct calendar *cal)
+{
+ sqlite3 *db;
+ if (sqlite3_open(cal->database, &db))
+ errx(1, "sqlite3_open: %s", sqlite3_errmsg(db));
+
+ const char *sql = "SELECT "
+ "unixepoch(start), "
+ "unixepoch(end), "
+ "CAST(strftime('%Y', start, 'localtime') as INT), "
+ "CAST(strftime('%m', start, 'localtime') as INT), "
+ "CAST(strftime('%d', start, 'localtime') as INT), "
+ "CAST(strftime('%w', start, 'localtime') as INT), "
+ "CAST(strftime('%H', start, 'localtime') as INT), "
+ "CAST(strftime('%M', start, 'localtime') as INT), "
+ "CAST(strftime('%H', end , 'localtime') as INT), "
+ "CAST(strftime('%M', end , 'localtime') as INT), "
+ "summary "
+ "FROM event "
+ "WHERE datetime('now', 'start of day') < start "
+ "AND summary IS NOT NULL "
+ "ORDER BY start";
+ sqlite3_stmt *stmt = NULL;
+ if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL))
+ errx(1, "sqlite3_prepare_v2: %s", sqlite3_errmsg(db));
+
+ size_t n = 0;
+ for (;;) {
+ int step = sqlite3_step(stmt);
+ if (SQLITE_DONE == step)
+ break;
+ if (SQLITE_ROW != step)
+ errx(1, "sqlite3_step: %s", sqlite3_errmsg(db));
+
+ if (MAX_EVENTS <= n) {
+ warnx("MAX_EVENTS exceeded at %zu", n);
+ break;
+ }
+
+ int start = sqlite3_column_int(stmt, 0);
+ int end = sqlite3_column_int(stmt, 1);
+ int year = sqlite3_column_int(stmt, 2);
+ int month = sqlite3_column_int(stmt, 3);
+ int day = sqlite3_column_int(stmt, 4);
+ int wday = sqlite3_column_int(stmt, 5);
+ int hour = sqlite3_column_int(stmt, 6);
+ int minute = sqlite3_column_int(stmt, 7);
+ int end_hour = sqlite3_column_int(stmt, 8);
+ int end_minute = sqlite3_column_int(stmt, 9);
+ const char *summary =
+ (const void *)sqlite3_column_text(stmt, 10);
+ assert(summary);
+
+ cal->events[n].start = start;
+ cal->events[n].end = end;
+ cal->events[n].year = year;
+ cal->events[n].month = month;
+ cal->events[n].day = day;
+ cal->events[n].hour = hour;
+ cal->events[n].minute = minute;
+ cal->events[n].end_hour = end_hour;
+ cal->events[n].end_minute = end_minute;
+ strncpy(cal->events[n].title, summary, SIZEOF_TITLE);
+ cal->events[n].wday = wday;
+ ++n;
+ }
+
+ if (sqlite3_finalize(stmt))
+ errx(1, "sqlite3_finalize: %s", sqlite3_errmsg(db));
+
+ if (sqlite3_close(db))
+ errx(1, "sqlite3_close: %s", sqlite3_errmsg(db));
+
+ cal->num_events = n;
+}
+
+struct ray {
+ Shader shader;
+ RenderTexture2D target;
+ int shader_u_time;
+
+ Color bg;
+ Color fg;
+ Color hd;
+ Color rd;
+
+ Font font_h;
+ Font font_p;
+};
+struct ray R;
+
+const char *the_non_ascii = "ÄÅÉËÞÜÚÍÓÖÁÐFGHÏŒØÆŒ©®BÑΜ"
+ "äåéëþüúíóöáðfghïœøæœ©®bñµß"
+ "«»";
+
+void
+ray_init(void)
+{
+ SetTraceLogLevel(LOG_WARNING);
+ InitWindow(SCREEN_W, SCREEN_H, "opplysning");
+ SetTargetFPS(60);
+
+ char *codes = calloc(128 + strlen(the_non_ascii), 1);
+ if (!codes)
+ err(1, "calloc");
+ for (int i = 1; i < 128; ++i)
+ codes[i] = i;
+ memcpy(codes + 128, the_non_ascii, strlen(the_non_ascii));
+ int ncp = 0;
+ int *cp = LoadCodepoints(codes + 1, &ncp);
+ R.font_h = LoadFontEx("font/adventpro-bold.ttf", 48, cp, ncp);
+ R.font_p =
+ LoadFontEx("font/BellCentennialStd-Address.ttf", 28, cp, ncp);
+ UnloadCodepoints(cp);
+ free(codes);
+
+ R.bg = RAYWHITE;
+ R.fg = BLACK;
+ R.hd = (Color){0xf0, 0x4a, 0x00, 0xff};
+ R.rd = (Color){0xd7, 0x21, 0x17, 0xff};
+
+ R.shader = LoadShader(0, "s.glsl");
+ R.shader_u_time = GetShaderLocation(R.shader, "u_time");
+ R.target = LoadRenderTexture(SCREEN_W, SCREEN_H);
+}
+
+void
+blur(int x, int y, Font f, Color c, char *s)
+{
+ Vector2 m = MeasureTextEx(f, s, f.baseSize, 0);
+
+ BeginTextureMode(R.target);
+ ClearBackground((Color){0});
+ DrawTextEx(f, s, (Vector2){0, 0}, (float)f.baseSize, 0, c);
+ EndTextureMode();
+
+ BeginShaderMode(R.shader);
+ float t = GetTime();
+ SetShaderValue(R.shader, R.shader_u_time, &t, SHADER_UNIFORM_FLOAT);
+ DrawTextureRec(R.target.texture,
+ (Rectangle){0, SCREEN_H - m.y, m.x, -m.y},
+ (Vector2){x, y}, WHITE);
+ EndShaderMode();
+}
+void
+line(int x, int y, Font f, Color c, const char *s)
+{
+ DrawTextEx(f, s, (Vector2){x, y}, (float)f.baseSize, 0, c);
+}
+
+void
+draw_time(int x, int y, struct timespec now)
+{
+ struct tm *ti = localtime(&now.tv_sec);
+ char ts[64] = {0};
+ snprintf(ts, sizeof(ts), "%02d:%02d", ti->tm_hour, ti->tm_min);
+ Vector2 v2_ts = MeasureTextEx(R.font_h, ts, R.font_h.baseSize, 0);
+ line(x - v2_ts.x / 2, y, R.font_h, R.fg, ts);
+}
+
+void
+draw_event(int x, int y, struct timespec now, const struct event *e)
+{
+ char s[128] = {0};
+ snprintf(s, sizeof(s), "%02u:%02u - %02u:%02u %s", e->hour, e->minute,
+ e->end_hour, e->end_minute, e->title);
+ Color c = R.fg;
+ if (e->start <= now.tv_sec && now.tv_sec < e->end)
+ c = R.rd;
+ line(x, y, R.font_p, c, s);
+}
+
+void
+draw_date(int x, int y, struct timespec now, int wday, int day, int month,
+ int year)
+{
+ struct tm *ti = localtime(&now.tv_sec);
+ Color c = R.fg;
+ if ((year == 1900 + ti->tm_year) && (month == 1 + ti->tm_mon) &&
+ (day == ti->tm_mday))
+ c = R.rd;
+ char s[64] = {0};
+ snprintf(s, sizeof(s), "%s %u. %s", ukedag[wday], day, maaned[month]);
+ line(x, y, R.font_h, c, s);
+}
+
+void
+draw_calendar(int x, int y, struct timespec now, struct calendar *cal)
+
+{
+ x += 20;
+ line(x, y, R.font_h, R.fg, cal->title);
+ y += 50;
+ line(x, y, R.font_p, R.fg, cal->subtitle);
+
+ int year = 0, month = 0, day = 0;
+ for (size_t i = 0; i < cal->num_events; ++i) {
+ const struct event *e = &cal->events[i];
+ if (year != e->year || month != e->month || day != e->day) {
+ if (SCREEN_H - 2 * R.font_h.baseSize < y)
+ break;
+ year = e->year;
+ month = e->month;
+ day = e->day;
+ y += 40;
+ draw_date(x, y, now, e->wday, e->day, e->month,
+ e->year);
+ y += 20;
+ }
+ y += 40;
+ draw_event(x, y, now, e);
+ }
+}
+
+int
+main(void)
+{
+ struct calendar google = {
+ .database = "var/google.db",
+ .title = "Reservasjoner",
+ .subtitle = "Hentet fra Google-kalenderen «Bitraf booking»",
+ };
+ struct calendar meetup = {
+ .database = "var/meetup.db",
+ .title = "Begivenheter",
+ .subtitle = "Hentet fra https://www.meetup.com/bitraf/",
+ };
+
+ ray_init();
+ while (!WindowShouldClose()) {
+ if (IsKeyPressed(KEY_R)) {
+ double t0 = GetTime();
+ load_events(&google);
+ load_events(&meetup);
+ double t1 = GetTime();
+ fprintf(stderr, "%6.3f ms\n", (t1 - t0) * 1000.0);
+ }
+
+ struct timespec now = {0};
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ BeginDrawing();
+ ClearBackground(R.bg);
+ draw_time(SCREEN_W / 2 - 100, 0, now);
+ draw_calendar(0, 0, now, &meetup);
+ draw_calendar(SCREEN_W / 2, 0, now, &google);
+ EndDrawing();
+ }
+ CloseWindow();
+}