view src/libmlk-rpg/rpg/message.c @ 378:460c78706989

misc: update copyright years
author David Demelier <markand@malikania.fr>
date Sun, 02 Jan 2022 10:22:48 +0100
parents 8f9937403749
children 9334b420c975
line wrap: on
line source

/*
 * message.c -- message dialog
 *
 * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <core/action.h>
#include <core/event.h>
#include <core/font.h>
#include <core/maths.h>
#include <core/painter.h>
#include <core/panic.h>
#include <core/sprite.h>
#include <core/trace.h>
#include <core/util.h>

#include <ui/align.h>
#include <ui/frame.h>
#include <ui/label.h>
#include <ui/theme.h>

#include "message.h"
#include "rpg_p.h"

#define THEME(msg)      (msg->theme ? msg->theme : theme_default())

static void
handle(struct action *action, const union event *ev)
{
	assert(action);
	assert(ev);

	message_handle(action->data, ev);
}

static int
update(struct action *action, unsigned int ticks)
{
	assert(action);

	return message_update(action->data, ticks);
}

static void
draw(struct action *action)
{
	assert(action);

	message_draw(action->data);
}

static void
draw_frame(const struct message *msg)
{
	assert(msg);

	struct frame frame = {
		.w = msg->w,
		.h = msg->h,
		.theme = msg->theme
	};

	frame_draw(&frame);
}

static inline unsigned int
min_width(const struct message *msg)
{
	assert(msg);

	unsigned int maxw = 0, w = 0;

	for (size_t i = 0; i < MESSAGE_LINES_MAX; ++i) {
		if (!msg->text[i])
			continue;
		if (font_query(THEME(msg)->fonts[THEME_FONT_INTERFACE], msg->text[i], &w, NULL) < 0)
			panic();
		if (w > maxw)
			maxw = w;
	}

	return (THEME(msg)->padding * 2) + maxw;
}

static inline unsigned int
min_height(const struct message *msg)
{
	assert(msg);

	const struct theme *th = THEME(msg);
	const unsigned int lh  = font_height(th->fonts[THEME_FONT_INTERFACE]);

	return (th->padding * 2) + (MESSAGE_LINES_MAX * lh) + ((MESSAGE_LINES_MAX - 1) * msg->spacing);
}

static void
draw_lines(const struct message *msg)
{
	struct theme theme;
	struct label label;
	unsigned int lw, lh;

	/*
	 * We need a copy of the current theme because we will alter the label
	 * color depending on the selection.
	 */
	theme_shallow(&theme, THEME(msg));

	for (int i = 0; i < MESSAGE_LINES_MAX; ++i) {
		if (!msg->text[i])
			continue;
		if (font_query(theme.fonts[THEME_FONT_INTERFACE], msg->text[i], &lw, &lh) < 0)
			panic();

		label.theme = &theme;
		label.x = theme.padding;
		label.y = theme.padding + (i * (lh + msg->spacing));
		label.text = msg->text[i];
		label.flags = LABEL_FLAGS_SHADOW;

		if (label.x + lw > msg->w)
			tracef(_("message width too small: %u < %u"), msg->w, min_width(msg));
		if (label.y + lh > msg->h)
			tracef(_("message height too small: %u < %u"), msg->h, min_height(msg));

		/*
		 * The function label_draw will use THEME_COLOR_NORMAL to draw
		 * text and THEME_COLOR_SHADOW so if we have selected a line
		 * we need to cheat the normal color.
		 */
		if (msg->flags & MESSAGE_FLAGS_QUESTION && msg->index == (unsigned int)i)
			theme.colors[THEME_COLOR_NORMAL] = THEME(msg)->colors[THEME_COLOR_SELECTED];
		else
			theme.colors[THEME_COLOR_NORMAL] = THEME(msg)->colors[THEME_COLOR_NORMAL];

		label_draw(&label);
	}
}

void
message_start(struct message *msg)
{
	assert(msg);

	if (msg->flags & (MESSAGE_FLAGS_FADEIN|MESSAGE_FLAGS_FADEOUT))
		assert(msg->delay > 0);

	msg->elapsed = 0;
	msg->scale = msg->flags & MESSAGE_FLAGS_FADEIN ? 0.0 : 1.0;
	msg->state = msg->flags & MESSAGE_FLAGS_FADEIN
	    ? MESSAGE_STATE_OPENING
	    : MESSAGE_STATE_SHOWING;

	if (msg->flags & MESSAGE_FLAGS_AUTOMATIC && msg->timeout == 0)
		tracef(_("message is automatic but has zero timeout"));
}

void
message_query(const struct message *msg, unsigned int *w, unsigned int *h)
{
	assert(msg);

	if (w)
		*w = min_width(msg);
	if (h)
		*h = min_height(msg);
}

void
message_handle(struct message *msg, const union event *ev)
{
	assert(msg);
	assert(ev);

	/* Skip if the message animation hasn't complete. */
	if (msg->state != MESSAGE_STATE_SHOWING)
		return;

	/* Only keyboard event are valid. */
	if (ev->type != EVENT_KEYDOWN || msg->state == MESSAGE_STATE_NONE)
		return;

	switch (ev->key.key) {
	case KEY_UP:
		if (msg->index > 0)
			msg->index--;
		break;
	case KEY_DOWN:
		if (msg->index < 5 && msg->text[msg->index + 1])
			msg->index++;
		break;
	case KEY_ENTER:
		msg->state = msg->flags & MESSAGE_FLAGS_FADEOUT
		    ? MESSAGE_STATE_HIDING
		    : MESSAGE_STATE_NONE;
		msg->elapsed = 0;
		break;
	default:
		break;
	}
}

int
message_update(struct message *msg, unsigned int ticks)
{
	assert(msg);

	msg->elapsed += ticks;

	switch (msg->state) {
	case MESSAGE_STATE_OPENING:
		msg->scale = (double)msg->elapsed / (double)msg->delay;

		if (msg->scale > 1)
			msg->scale = 1;

		if (msg->elapsed >= msg->delay) {
			msg->state = MESSAGE_STATE_SHOWING;
			msg->elapsed = 0;
		}

		break;
	case MESSAGE_STATE_SHOWING:
		/* Do automatically switch state if requested by the user. */
		if (msg->flags & MESSAGE_FLAGS_AUTOMATIC && msg->elapsed >= msg->timeout) {
			msg->state = msg->flags & MESSAGE_FLAGS_FADEOUT
			    ? MESSAGE_STATE_HIDING
			    : MESSAGE_STATE_NONE;
			msg->elapsed = 0;
		}

		break;
	case MESSAGE_STATE_HIDING:
		msg->scale = 1 - (double)msg->elapsed / (double)msg->delay;

		if (msg->scale < 0)
			msg->scale = 0;
		if (msg->elapsed >= msg->delay) {
			msg->state = MESSAGE_STATE_NONE;
			msg->elapsed = 0;
		}

		break;
	default:
		break;
	}

	return msg->state == MESSAGE_STATE_NONE;
}

void
message_draw(const struct message *msg)
{
	assert(msg);

	struct texture tex;
	int x, y;
	unsigned int w, h;

	if (msg->w == 0 || msg->h == 0) {
		tracef(_("message has null dimensions"));
		return;
	}

	if (texture_new(&tex, msg->w, msg->h) < 0)
		panic();

	PAINTER_BEGIN(&tex);
	draw_frame(msg);
	draw_lines(msg);
	PAINTER_END();

	/* Compute scaling. */
	w = msg->w * msg->scale;
	h = msg->h * msg->scale;

	/* Centerize within its drawing area. */
	align(ALIGN_CENTER, &x, &y, w, h, msg->x, msg->y, msg->w, msg->h);

	/* Draw and clear. */
	texture_scale(&tex, 0, 0, msg->w, msg->h, x, y, w, h, 0);
	texture_finish(&tex);
}

void
message_hide(struct message *msg)
{
	assert(msg);

	msg->state = MESSAGE_STATE_HIDING;
	msg->elapsed = 0;
}

void
message_action(struct message *msg, struct action *action)
{
	assert(msg);
	assert(action);

	memset(action, 0, sizeof (struct action));
	action->data = msg;
	action->handle = handle;
	action->update = update;
	action->draw = draw;

	message_start(msg);
}