Add SDL driver prototype. Update sdlterm: Handle keyboard, mouse events. Add glyph cache.
#include "sdlterm.h"
#include <stdexcept>
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)
{
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_glyph(Uint16 ch)
{
SDL_Surface *cell_surface;
// try cache
cell_surface = _cache.lookup_glyph(ch);
if (cell_surface)
{
return cell_surface;
}
TTF_Font *font = _font_regular;
SDL_Color color={0xff,0xff,0xff}, bgcolor={0,100,100};
if (ch != 'W')
{
bgcolor.g = 0;
bgcolor.b = 0;
}
// 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
SDL_Surface *glyph_surface = TTF_RenderGlyph_Shaded(font, ch, color, 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);
_cache.put_glyph(ch, cell_surface);
return cell_surface;
}
TerminalScreen::~TerminalScreen()
{
if (_cells)
{
delete[] _cells;
}
}
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, 32, SDL_HWSURFACE|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()
{
TerminalCell * cell = _cells;
for (int i = 0; i < _width * _height; i++)
{
cell->ch = ' ';
cell->attr = 0;
cell++;
}
}
void TerminalScreen::putch(int x, int y, Uint16 ch, Uint16 attr)
{
TerminalCell &cell = _cells[y * _width + x];
cell.ch = ch;
cell.attr = attr;
}
void TerminalScreen::commit()
{
TerminalCell * cell = _cells;
SDL_Surface *glyph_surface;
SDL_Rect dst_rect;
for (int y = 0; y < _height; y++)
{
for (int x = 0; x < _width; x++)
{
dst_rect.x = x * _cell_width;
dst_rect.y = y * _cell_height;
glyph_surface = _render.render_glyph(cell->ch);
SDL_BlitSurface(glyph_surface, NULL, _screen_surface, &dst_rect);
cell++;
}
}
SDL_UpdateRect(_screen_surface, 0, 0, 0, 0);
}
void TerminalScreen::_reset_cells()
{
if (_cells)
{
delete[] _cells;
_cells = NULL;
}
_cell_width = _render.get_cell_width();
_cell_height = _render.get_cell_height();
_width = _pixel_width / _cell_width;
_height = _pixel_height / _cell_height;
int num_cells = _width * _height;
_cells = new TerminalCell[num_cells];
}
Terminal::Terminal()
: _screen()
{
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)
{
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;
event.key.unicode = sdl_event.key.keysym.unicode;
strncpy(event.key.keyname, _translate_keyname(sdl_event.key.keysym.sym), 10);
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;
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();
return;
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_PAUSE: return "pause";
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";
default: return "";
}
}