static void UI_MaterialEditorNewStage_f (void)
{
	material_t* m;
	int id;

	if (Cmd_Argc() < 2) {
		Com_Printf("Usage: %s <image index>\n", Cmd_Argv(0));
		return;
	}

	id = atoi(Cmd_Argv(1));
	if (id < 0 || id >= r_numImages) {
		Com_Printf("Given image index (%i) is out of bounds\n", id);
		return;
	}

	m = &R_GetImageAtIndex(id)->material;
	materialStage_t* const s = Mem_PoolAllocType(materialStage_t, vid_imagePool);
	m->num_stages++;

	/* append the stage to the chain */
	if (!m->stages)
		m->stages = s;
	else {
		materialStage_t* ss = m->stages;
		while (ss->next)
			ss = ss->next;
		ss->next = s;
	}

	UI_MaterialEditorUpdate(R_GetImageAtIndex(id), s);
}
static void UI_MaterialEditorSelectStage_f (void)
{
	image_t* image;
	int id, stageID;
	materialStage_t* materialStage;

	if (Cmd_Argc() < 3) {
		Com_Printf("Usage: %s <image index> <stage index>\n", Cmd_Argv(0));
		return;
	}

	id = atoi(Cmd_Argv(1));
	if (id < 0 || id >= r_numImages) {
		Com_Printf("Given image index (%i) is out of bounds\n", id);
		return;
	}

	image = R_GetImageAtIndex(id);

	stageID = atoi(Cmd_Argv(2));
	if (stageID < 0 || stageID >= image->material.num_stages) {
		Com_Printf("Given stage index (%i) is out of bounds\n", stageID);
		return;
	}

	materialStage = UI_MaterialEditorGetStage(&image->material, stageID);
	UI_MaterialEditorUpdate(image, materialStage);
}
static void UI_MaterialEditorRemoveStage_f (void)
{
	image_t* image;
	int id, stageID;

	if (Cmd_Argc() < 3) {
		Com_Printf("Usage: %s <image index> <stage index>\n", Cmd_Argv(0));
		return;
	}

	id = atoi(Cmd_Argv(1));
	if (id < 0 || id >= r_numImages) {
		Com_Printf("Given image index (%i) is out of bounds\n", id);
		return;
	}

	image = R_GetImageAtIndex(id);

	stageID = atoi(Cmd_Argv(2));
	if (stageID < 0 || stageID >= image->material.num_stages) {
		Com_Printf("Given stage index (%i) is out of bounds\n", stageID);
		return;
	}

	materialStage_t** const anchor = stageID == 0 ? &image->material.stages : &UI_MaterialEditorGetStage(&image->material, stageID - 1)->next;
	materialStage_t*  const s      = *anchor;
	*anchor = s->next;
	Mem_Free(s);

	image->material.num_stages--;

	UI_MaterialEditorUpdate(image, nullptr);
}
/**
 * @brief Return index of the image (r_images[i]) else NULL
 */
static int UI_MaterialEditorNodeGetImageAtPosition (uiNode_t *node, int x, int y)
{
	int i;
	vec2_t pos;
	int cnt = 0;
	int cntView = 0;
	const int imagesPerLine = (node->box.size[0] - node->padding) / (IMAGE_WIDTH + node->padding);
	const int imagesPerColumn = (node->box.size[1] - node->padding) / (IMAGE_WIDTH + node->padding);
	int columnRequested;
	int lineRequested;

	UI_NodeAbsoluteToRelativePos(node, &x, &y);

	/* have we click between 2 images? */
	if (((x % (IMAGE_WIDTH + node->padding)) < node->padding)
		|| ((y % (IMAGE_WIDTH + node->padding)) < node->padding))
		return -1;

	/* get column and line of the image */
	columnRequested = x / (IMAGE_WIDTH + node->padding);
	lineRequested = y / (IMAGE_WIDTH + node->padding);

	/* have we click outside? */
	if (columnRequested >= imagesPerLine || lineRequested >= imagesPerColumn)
		return -1;

	UI_GetNodeAbsPos(node, pos);

	/* check images */
	for (i = 0; i < r_numImages; i++) {
#ifndef ANYIMAGES
		/* filter */
		image_t *image = R_GetImageAtIndex(i);
		if (image->type != it_world)
			continue;

		if (strstr(image->name, "tex_common"))
			continue;
#endif

		/* skip images before the scroll position */
		if (cnt / imagesPerLine < EXTRADATA(node).scrollY.viewPos) {
			cnt++;
			continue;
		}

		if (cntView % imagesPerLine == columnRequested && cntView / imagesPerLine == lineRequested)
			return i;

		/* vertical overflow */
		if (cntView / imagesPerLine > lineRequested)
			break;

		cnt++;
		cntView++;
	}

	return -1;
}
/**
 * @param node The node to draw
 */
void uiMaterialEditorNode::draw (uiNode_t *node)
{
	int i;
	vec2_t pos;
	int cnt = 0;
	int cntView = 0;
	const int imagesPerLine = (node->box.size[0] - node->padding) / (IMAGE_WIDTH + node->padding);

	if (isSizeChange(node))
		updateView(node, false);

	/* width too small to display anything */
	if (imagesPerLine <= 0)
		return;

	UI_GetNodeAbsPos(node, pos);

	/* display images */
	for (i = 0; i < r_numImages; i++) {
		image_t *image = R_GetImageAtIndex(i);
		vec2_t imagepos;

#ifndef ANYIMAGES
		/* filter */
		if (image->type != it_world)
			continue;

		if (strstr(image->name, "tex_common"))
			continue;
#endif

		/* skip images before the scroll position */
		if (cnt / imagesPerLine < EXTRADATA(node).scrollY.viewPos) {
			cnt++;
			continue;
		}

		/** @todo do it incremental. Don't need all this math */
		imagepos[0] = pos[0] + node->padding + (cntView % imagesPerLine) * (IMAGE_WIDTH + node->padding);
		imagepos[1] = pos[1] + node->padding + (cntView / imagesPerLine) * (IMAGE_WIDTH + node->padding);

		/* vertical overflow */
		if (imagepos[1] + IMAGE_WIDTH + node->padding >= pos[1] + node->box.size[1])
			break;

		if (i == node->num) {
#define MARGIN 3
			UI_DrawRect(imagepos[0] - MARGIN, imagepos[1] - MARGIN, IMAGE_WIDTH + MARGIN * 2, IMAGE_WIDTH + MARGIN * 2, node->selectedColor, 2, 0xFFFF);
#undef MARGIN
		}

		UI_DrawNormImage(false, imagepos[0], imagepos[1], IMAGE_WIDTH, IMAGE_WIDTH, 0, 0, 0, 0, image);

		cnt++;
		cntView++;
	}
}
/**
 * @brief return the number of images we can display
 */
static int UI_MaterialEditorNodeGetImageCount (uiNode_t* node)
{
	int cnt = 0;

	for (int i = 0; i < r_numImages; i++) {
#ifndef ANYIMAGES
		const image_t* image = R_GetImageAtIndex(i);
		/* filter */
		if (image->type != it_world)
			continue;

		if (strstr(image->name, "tex_common"))
			continue;
#endif
		cnt++;
	}
	return cnt;
}
void uiMaterialEditorNode::onMouseDown (uiNode_t* node, int x, int y, int button)
{
	int id;
	if (button != K_MOUSE1)
		return;

	id = UI_MaterialEditorNodeGetImageAtPosition(node, x, y);
	if (id == -1)
		return;

	/** @note here we use "num" to cache the selected image id. We can reuse it on the script with "<num>" */
	/* have we selected a new image? */
	if (node->num != id) {
		image_t* image = R_GetImageAtIndex(id);
		UI_MaterialEditorUpdate(image, nullptr);

		node->num = id;

		if (node->onChange)
			UI_ExecuteEventActions(node, node->onChange);
	}
}
static void UI_MaterialEditorRemoveStage_f (void)
{
	image_t *image;
	int id, stageID;

	if (Cmd_Argc() < 3) {
		Com_Printf("Usage: %s <image index> <stage index>\n", Cmd_Argv(0));
		return;
	}

	id = atoi(Cmd_Argv(1));
	if (id < 0 || id >= r_numImages) {
		Com_Printf("Given image index (%i) is out of bounds\n", id);
		return;
	}

	image = R_GetImageAtIndex(id);

	stageID = atoi(Cmd_Argv(2));
	if (stageID < 0 || stageID >= image->material.num_stages) {
		Com_Printf("Given stage index (%i) is out of bounds\n", stageID);
		return;
	}

	if (stageID == 0) {
		materialStage_t *s = image->material.stages;
		image->material.stages = s->next;
		Mem_Free(s);
	} else {
		materialStage_t *sParent = UI_MaterialEditorGetStage(&image->material, stageID - 1);
		materialStage_t *s = sParent->next;
		sParent->next = s->next;
		Mem_Free(s);
	}

	image->material.num_stages--;

	UI_MaterialEditorUpdate(image, NULL);
}
static void UI_MaterialEditorChangeValue_f (void)
{
	image_t* image;
	int id, stageType;
	const char* var, *value;
	size_t bytes;

	if (Cmd_Argc() < 5) {
		Com_Printf("Usage: %s <image index> <stage index> <variable> <value>\n", Cmd_Argv(0));
		return;
	}

	id = atoi(Cmd_Argv(1));
	if (id < 0 || id >= r_numImages) {
		Com_Printf("Given image index (%i) is out of bounds\n", id);
		return;
	}

	var = Cmd_Argv(3);
	value = Cmd_Argv(4);

	image = R_GetImageAtIndex(id);

	stageType = UI_MaterialEditorNameToStage(var);
	if (stageType == -1) {
		const value_t* val = UI_FindPropertyByName(materialValues, var);
		if (!val) {
			Com_Printf("Could not find material variable for '%s'\n", var);
			return;
		}
		Com_ParseValue(&image->material, value, val->type, val->ofs, val->size, &bytes);
	} else {
		materialStage_t* stage;
		int stageID;
		const value_t* val = UI_FindPropertyByName(materialStageValues, var);

		if (!val) {
			Com_Printf("Could not find material stage variable for '%s'\n", var);
			return;
		}

		stageID = atoi(Cmd_Argv(2));
		if (stageID < 0 || stageID >= image->material.num_stages) {
			Com_Printf("Given stage index (%i) is out of bounds\n", stageID);
			return;
		}

		stage = UI_MaterialEditorGetStage(&image->material, stageID);
		assert(stage);

		stage->flags |= stageType;

		Com_ParseValue(stage, value, val->type, val->ofs, val->size, &bytes);

		/* a texture or envmap means render it */
		if (stage->flags & (STAGE_TEXTURE | STAGE_ENVMAP))
			stage->flags |= STAGE_RENDER;

		if (stage->flags & (STAGE_TAPE | STAGE_TERRAIN | STAGE_DIRTMAP))
			stage->flags |= STAGE_LIGHTING;
	}

	R_ModReloadSurfacesArrays();
}