sdlterm/src/sdlterm.cc
author Radek Brich <radek.brich@devl.cz>
Sat, 05 Jan 2013 18:44:56 +0100
changeset 50 c5b8b9d2da95
parent 49 1611c462c3e3
child 51 dce7325109c1
permissions -rw-r--r--
DriverSDL: Implement colors.

#include "sdlterm.h"

#include <exception>
#include <algorithm>


void ColorMap::index_to_rgb(int index, SDL_Color &color)
{
	color.r = _map[index][0];
	color.g = _map[index][1];
	color.b = _map[index][2];
}


SDL_Surface *GlyphCache::lookup_glyph(Uint16 ch)
{
	auto iter = _glyph_map.find(ch);
	if (iter == _glyph_map.end())
	{
		return NULL;
	}
	return iter->second;
}


void GlyphCache::put_glyph(Uint16 ch, SDL_Surface *srf)
{
	_glyph_map[ch] = srf;
}


void GlyphCache::flush()
{
	for (auto iter = _glyph_map.begin(); iter != _glyph_map.end(); iter++)
	{
		SDL_FreeSurface(iter->second);
	}
	_glyph_map.clear();
}


GlyphRenderer::GlyphRenderer()
 : _font_regular(NULL), _font_bold(NULL), _cell_width(0), _cell_height(0), _cache(), _colormap()
{
	if (TTF_Init() == -1)
	{
	    printf("TTF_Init: %s\n", TTF_GetError());
	    throw std::exception();
	}
}


GlyphRenderer::~GlyphRenderer()
{
	_cache.flush();
	close_font();
	TTF_Quit();
}


void GlyphRenderer::open_font(const char *fname_regular, const char *fname_bold, int ptsize)
{
	close_font();

	// open regular font
	_font_regular = TTF_OpenFont(fname_regular, ptsize);
	if (!_font_regular)
	{
		printf("TTF_OpenFont: %s\n", TTF_GetError());
		throw std::exception();
	}

	// open bold font
	_font_bold = TTF_OpenFont(fname_bold, ptsize);
	if (!_font_bold)
	{
		printf("TTF_OpenFont: %s\n", TTF_GetError());
		throw std::exception();
	}

	// update metrics for regular font
	int advance;
	if (TTF_GlyphMetrics(_font_regular, 'M', NULL, NULL, NULL, NULL, &advance) == -1)
	{
	    printf("TTF_GlyphMetrics: %s\n", TTF_GetError());
	}
	_cell_width = advance;
	_cell_height = TTF_FontHeight(_font_regular);

	// read metrics for bold font
	if (TTF_GlyphMetrics(_font_bold, 'M', NULL, NULL, NULL, NULL, &advance) == -1)
	{
		printf("TTF_GlyphMetrics: %s\n", TTF_GetError());
	}
	if (advance > _cell_width)
	{
		_cell_width = advance;
	}
	int height = TTF_FontHeight(_font_bold);
	if (height > _cell_height)
	{
		_cell_height = height;
	}
}


void GlyphRenderer::close_font()
{
	if (_font_regular)
	{
		TTF_CloseFont(_font_regular);
		_font_regular = NULL;
	}
	if (_font_bold)
	{
		TTF_CloseFont(_font_bold);
		_font_bold = NULL;
	}
}


SDL_Surface *GlyphRenderer::render_cell(Uint16 ch, Uint16 attr)
{
	SDL_Surface *cell_surface;
	TTF_Font *font;
	SDL_Color fgcolor, bgcolor;

	// try cache
	cell_surface = _cache.lookup_glyph(ch);
	if (cell_surface)
	{
		return cell_surface;
	}

	// load attributes
	_colormap.index_to_rgb(attr & 0x000F, fgcolor);
	_colormap.index_to_rgb((attr & 0x00F0) >> 4, bgcolor);
	Style style = (Style) ((attr & 0xFF00) >> 8);
	font = (style == Style::BOLD) ? _font_bold : _font_regular;

	// create surface for whole cell and fill it with bg color
	cell_surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
			_cell_width, _cell_height, 32, 0, 0, 0, 0);
	SDL_Rect dst_rect;
	dst_rect.x = 0;
	dst_rect.y = 0;
	dst_rect.w = _cell_width;
	dst_rect.h = _cell_height;
	Uint32 bgcolor_mapped = SDL_MapRGB(cell_surface->format, bgcolor.r, bgcolor.g, bgcolor.b);
	SDL_FillRect(cell_surface, &dst_rect, bgcolor_mapped);

	// render glyph, blit it onto cell surface
	if (ch)
	{
		SDL_Surface *glyph_surface = TTF_RenderGlyph_Shaded(font, ch, fgcolor, bgcolor);
		int minx, maxy;
		TTF_GlyphMetrics(font, ch, &minx, NULL, NULL, &maxy, NULL);
		dst_rect.x = minx;
		dst_rect.y = TTF_FontAscent(font) - maxy;
		SDL_BlitSurface(glyph_surface, NULL, cell_surface, &dst_rect);
		SDL_FreeSurface(glyph_surface);
	}

	// convert to display format
	SDL_Surface *tmp_surface = cell_surface;
	cell_surface = SDL_DisplayFormat(tmp_surface);
	SDL_FreeSurface(tmp_surface);

	// put to cache
	_cache.put_glyph(ch, cell_surface);

	return cell_surface;
}


void TerminalScreen::select_font(const char *fname_regular, const char *fname_bold, int ptsize)
{
	_render.open_font(fname_regular, fname_bold, ptsize);
	_reset_cells();
}

void TerminalScreen::resize(int pxwidth, int pxheight)
{
    _screen_surface = SDL_SetVideoMode(pxwidth, pxheight, 0, SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);

    if (_screen_surface == NULL)
    {
		fprintf(stderr, "Unable to set video: %s\n", SDL_GetError());
		throw std::exception();
    }

    SDL_WM_SetCaption("terminal", NULL);

    _pixel_width = pxwidth;
    _pixel_height = pxheight;

    _reset_cells();
}


void TerminalScreen::erase()
{
	std::fill(_cells_front.begin(), _cells_front.end(), TerminalCell());
}


void TerminalScreen::putch(int x, int y, Uint16 ch, Uint16 attr)
{
	TerminalCell &cell = _cells_front[y * _width + x];
	cell.ch = ch;
	cell.attr = attr;
}


void TerminalScreen::commit()
{
	auto front_iter = _cells_front.begin();
	auto back_iter = _cells_back.begin();
	SDL_Surface *cell_surface;
	SDL_Rect dst_rect;
	for (int y = 0; y < _height; y++)
	{
		for (int x = 0; x < _width; x++)
		{
			if (*front_iter != *back_iter)
			{
				dst_rect.x = x * _cell_width;
				dst_rect.y = y * _cell_height;
				cell_surface = _render.render_cell(front_iter->ch, front_iter->attr);
				SDL_BlitSurface(cell_surface, NULL, _screen_surface, &dst_rect);
				*back_iter = *front_iter;
			}
			front_iter++;
			back_iter++;
		}
	}

	SDL_UpdateRect(_screen_surface, 0, 0, 0, 0);
}


void TerminalScreen::_reset_cells()
{
	_cell_width = _render.get_cell_width();
	_cell_height = _render.get_cell_height();
	if (!_cell_width || !_cell_height)
		return;

	_width = _pixel_width / _cell_width;
	_height = _pixel_height / _cell_height;
	if (!_width || !_height)
		return;

	int num_cells = _width * _height;
	_cells_front.resize(num_cells, TerminalCell());
	_cells_back.resize(num_cells, TerminalCell());
}


Terminal::Terminal()
 : _screen(), _attr(7), _mousemove_last_x(-1)
{
    if (SDL_Init(SDL_INIT_VIDEO) == -1)
    {
		fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
		throw std::exception();
    }
    SDL_EnableUNICODE(1);
}


Terminal::~Terminal()
{
	SDL_Quit();
}


void Terminal::get_next_event(Event &event)
{
	static SDL_Event sdl_event;

	while (SDL_WaitEvent(&sdl_event))
	{
		switch (sdl_event.type)
		{
			case SDL_QUIT:
				event.type = Event::QUIT;
				return;

			case SDL_KEYDOWN:
			{
				//switch(event.key.keysym.sym)
				event.type = Event::KEYPRESS;
				const char *keyname = _translate_keyname(sdl_event.key.keysym.sym);
				// return only keyname or unicode, never both
				if (keyname)
				{
					strncpy(event.key.keyname, keyname, 10);
					event.key.unicode = 0;
				}
				else
				{
					event.key.keyname[0] = 0;
					event.key.unicode = sdl_event.key.keysym.unicode;
					if (!event.key.unicode)
						break; // continue loop (unknown key)
				}
				return;
			}

			case SDL_MOUSEBUTTONDOWN:
			case SDL_MOUSEBUTTONUP:
				event.type = (sdl_event.type == SDL_MOUSEBUTTONDOWN) ? Event::MOUSEDOWN : Event::MOUSEUP;
				event.mouse.x = sdl_event.button.x / _screen.get_cell_width();
				event.mouse.y = sdl_event.button.y / _screen.get_cell_height();
				event.mouse.button = sdl_event.button.button;
				_mousemove_last_x = -1;
				return;

			case SDL_MOUSEMOTION:
				event.type = Event::MOUSEMOVE;
				event.mouse.x = sdl_event.motion.x / _screen.get_cell_width();
				event.mouse.y = sdl_event.motion.y / _screen.get_cell_height();
				if (_mousemove_last_x != event.mouse.x ||
		            _mousemove_last_y != event.mouse.y)
				{
					_mousemove_last_x = event.mouse.x;
					_mousemove_last_y = event.mouse.y;
					return;
				}
				break; // continue loop when mouse position did not change

			default:
				break; // continue loop
		}
	}
}


const char *Terminal::_translate_keyname(SDLKey sym)
{
	switch (sym)
	{
		case SDLK_BACKSPACE: 	return "backspace";
		case SDLK_TAB:			return "tab";
		case SDLK_RETURN:		return "enter";
		case SDLK_KP_ENTER:		return "enter";
		case SDLK_ESCAPE:		return "escape";
		case SDLK_DELETE:		return "delete";
		case SDLK_INSERT:		return "insert";
		case SDLK_UP:			return "up";
		case SDLK_DOWN:			return "down";
		case SDLK_LEFT:			return "left";
		case SDLK_RIGHT:		return "right";
		case SDLK_HOME:			return "home";
		case SDLK_END:			return "end";
		case SDLK_PAGEUP:		return "pageup";
		case SDLK_PAGEDOWN:		return "pagedown";
		case SDLK_F1:			return "f1";
		case SDLK_F2:			return "f2";
		case SDLK_F3:			return "f3";
		case SDLK_F4:			return "f4";
		case SDLK_F5:			return "f5";
		case SDLK_F6:			return "f6";
		case SDLK_F7:			return "f7";
		case SDLK_F8:			return "f8";
		case SDLK_F9:			return "f9";
		case SDLK_F10:			return "f10";
		case SDLK_F11:			return "f11";
		case SDLK_F12:			return "f12";
		case SDLK_PRINT:		return "print";
		case SDLK_SCROLLOCK:	return "scrllock";
		case SDLK_PAUSE:	    return "pause";
		default: return NULL;
	}
}