Add sdlterm prototype: extension module for SDL driver.
authorRadek Brich <radek.brich@devl.cz>
Sat, 05 Jan 2013 00:40:27 +0100
changeset 47 537d7c6b48a2
parent 46 2b43a7f38c34
child 48 1f00e90fd72a
Add sdlterm prototype: extension module for SDL driver.
.hgignore
sdlterm/Makefile
sdlterm/cython/sdlterm.pyx
sdlterm/demo.cc
sdlterm/demo.py
sdlterm/setup.py
sdlterm/src/sdlterm.cc
sdlterm/src/sdlterm.h
--- a/.hgignore	Sat Jan 05 00:37:11 2013 +0100
+++ b/.hgignore	Sat Jan 05 00:40:27 2013 +0100
@@ -1,9 +1,14 @@
-.*~
-^tuikit/.*\.pyc
+.*~$
+^tuikit/.*\.pyc$
 ^docs/_build
 ^tuikit\.log
 ^build
-\.project
-\.pydevproject
-\.settings
+^sdlterm/build
+^sdlterm/font
+^sdlterm/cython/sdlterm.cpp$
+^sdlterm/demo$
+^\.project
+^\.pydevproject
+^\.settings
 .*\.appstats
+__pycache__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/Makefile	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,8 @@
+CXXFLAGS=`sdl-config --cflags` -g -Wall -std=c++11 -Isrc
+LDFLAGS=`sdl-config --libs` -lSDL_ttf
+VPATH=src
+
+demo: demo.cc sdlterm.cc sdlterm.h
+
+clean:
+	rm -f demo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/cython/sdlterm.pyx	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,64 @@
+# distutils: language = c++
+# distutils: sources = src/sdlterm.cc
+# distutils: include_dirs = /usr/include/SDL src
+# distutils: libraries = SDL SDL_ttf
+# distutils: define_macros = _GNU_SOURCE=1 _REENTRANT
+# distutils: extra_compile_args = --std=c++0x
+# cython: language_level=3
+
+from libcpp cimport bool
+
+
+cdef extern from "sdlterm.h":
+    enum EventType:
+        pass
+
+    union Event:
+        EventType type
+
+    cdef cppclass Terminal:
+        Terminal() except +
+
+        void select_font(char *fname_regular, char *fname_bold, int ptsize)
+        void resize(int pxwidth, int pxheight)
+
+        void erase()
+        void putch(int x, int y, Py_UNICODE ch)
+        void commit()
+
+        int prepare_attr(int fg, int bg, int style)
+        void set_attr(int value)
+
+        void set_cursor(int x, int y)
+        void show_cursor(bool visible)
+
+        void get_next_event(Event event)
+
+
+cdef class SDLTerminal:
+    cdef Terminal *thisptr      # hold a C++ instance which we're wrapping
+
+    def __cinit__(self):
+        self.thisptr = new Terminal()
+    def __dealloc__(self):
+        del self.thisptr
+
+    def select_font(self, fname_regular, fname_bold, ptsize):
+        fname_regular = fname_regular.encode('utf8')
+        fname_bold = fname_bold.encode('utf8')
+        self.thisptr.select_font(fname_regular, fname_bold, ptsize)
+    def resize(self, width, height):
+        self.thisptr.resize(width, height)
+
+    def erase(self):
+        self.thisptr.erase()
+    def putch(self, x, y, ch):
+        self.thisptr.putch(x, y, ch)
+    def commit(self):
+        self.thisptr.commit()
+
+    def get_next_event(self):
+        cdef Event event
+        self.thisptr.get_next_event(event)
+        return dict()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/demo.cc	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,56 @@
+#include "sdlterm.h"
+
+
+class Application
+{
+public:
+	Terminal term;
+	bool done;
+
+	Application() : term(), done(false) {};
+
+	void init();
+	void wait_and_process_event();
+};
+
+
+void Application::init()
+{
+	term.resize(800, 600);
+	term.select_font("font/DejaVuSansMono.ttf", "font/DejaVuSansMono-Bold.ttf", 12);
+	term.erase();
+	term.putch(5, 5, 'W');
+	term.commit();
+}
+
+
+void Application::wait_and_process_event()
+{
+	Event event;
+	term.get_next_event(event);
+
+	switch (event.type)
+	{
+		case EventType::quit:
+			done = true;
+			break;
+
+		default:
+			break;
+	}
+}
+
+
+int main(int argc, char *argv[])
+{
+	Application app;
+	app.init();
+
+    while (!app.done)
+    {
+    	app.wait_and_process_event();
+    }
+
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/demo.py	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import sys
+sys.path.insert(0, 'build/lib.linux-x86_64-3.2')
+
+from sdlterm import SDLTerminal
+
+if __name__ == '__main__':
+    term = SDLTerminal()
+    term.resize(800, 600)
+    term.select_font("font/DejaVuSansMono.ttf", "font/DejaVuSansMono-Bold.ttf", 12)
+    term.erase()
+    term.putch(5, 5, 'W')
+    term.commit()
+
+    while True:
+        event = term.get_next_event()
+        break
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/setup.py	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,7 @@
+from distutils.core import setup
+from Cython.Build import cythonize
+
+setup(
+    ext_modules = cythonize("cython/sdlterm.pyx")
+)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/src/sdlterm.cc	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,251 @@
+#include "sdlterm.h"
+
+#include <stdexcept>
+
+
+GlyphRenderer::GlyphRenderer()
+ : _font_regular(NULL), _font_bold(NULL)
+{
+	if (TTF_Init() == -1)
+	{
+	    printf("TTF_Init: %s\n", TTF_GetError());
+	    throw std::exception();
+	}
+}
+
+
+GlyphRenderer::~GlyphRenderer()
+{
+	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)
+{
+	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
+	SDL_Surface *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);
+
+	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, 8, 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()
+{
+	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);
+			SDL_FreeSurface(glyph_surface);
+			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();
+    }
+}
+
+
+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 = EventType::quit;
+				return;
+
+			case SDL_KEYDOWN:
+				//switch(event.key.keysym.sym)
+				event.type = EventType::keypress;
+				return;
+
+			default:
+				break; // continue loop
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sdlterm/src/sdlterm.h	Sat Jan 05 00:40:27 2013 +0100
@@ -0,0 +1,135 @@
+#include "SDL.h"
+#include "SDL_ttf.h"
+
+#include <map>
+#include <vector>
+
+
+class GlyphRenderer
+{
+public:
+	GlyphRenderer();
+	~GlyphRenderer();
+
+	void open_font(const char *fname_regular, const char *fname_bold, int ptsize);
+	void close_font();
+
+	SDL_Surface *render_glyph(Uint16 ch);
+
+	int get_cell_width() { return _cell_width; };
+	int get_cell_height() { return _cell_height; };
+
+private:
+	TTF_Font *_font_regular;
+	TTF_Font *_font_bold;
+	int _cell_width;
+	int _cell_height;
+	std::map<Uint16, SDL_Surface*> _cache;
+};
+
+
+struct TerminalCell
+{
+	Uint16 ch;
+	Uint16 attr;
+};
+
+
+class TerminalScreen
+{
+public:
+	TerminalScreen(): _screen_surface(NULL), _cells(NULL), _render() {};
+	~TerminalScreen();
+
+	void select_font(const char *fname_regular, const char *fname_bold, int ptsize);
+	void resize(int pxwidth, int pxheight);
+
+	void erase();
+	void putch(int x, int y, Uint16 ch, Uint16 attr);
+	void commit();
+
+private:
+	SDL_Surface *_screen_surface;
+	TerminalCell *_cells;
+	GlyphRenderer _render;
+
+	int _pixel_width;  // terminal window width in pixels
+	int _pixel_height;
+	int _width;  // width in characters
+	int _height; // height in characters
+	int _cell_width;  // character cell width in pixels
+	int _cell_height;
+
+	void _reset_cells();
+};
+
+
+enum class EventType : Uint8
+{
+	quit,
+	resize,
+	keypress,
+	mousedown,
+	mouseup,
+	mousemove,
+	mousewheel
+};
+
+
+struct WindowEvent
+{
+	EventType type;
+};
+
+
+struct KeyboardEvent
+{
+	EventType type;
+};
+
+
+struct MouseEvent
+{
+	EventType type;
+};
+
+
+union Event
+{
+	EventType type;
+	WindowEvent window;
+	KeyboardEvent key;
+	MouseEvent mouse;
+};
+
+
+class Terminal
+{
+public:
+	Terminal();
+	~Terminal();
+
+	void select_font(const char *fname_regular, const char *fname_bold, int ptsize)
+		{ _screen.select_font(fname_regular, fname_bold, ptsize); };
+	void resize(int pxwidth, int pxheight) { _screen.resize(pxwidth, pxheight); };
+
+	void erase() { _screen.erase(); };
+	void putch(int x, int y, Uint16 ch) { _screen.putch(x, y, ch, _attr); };
+	void commit() { _screen.commit(); };
+
+	Uint16 prepare_attr(Uint8 fg, Uint8 bg, Uint8 style) { return fg | bg << 8 | style << 16; };
+	void set_attr(Uint16 value) { _attr = value; };
+
+	void set_cursor(int x, int y) { _cursor_x = x; _cursor_y = y; };
+	void show_cursor(bool visible) { _cursor_visible = visible; };
+
+	void get_next_event(Event &event);
+
+private:
+	TerminalScreen _screen;
+	Uint16 _attr;
+	int _cursor_x;
+	int _cursor_y;
+	bool _cursor_visible;
+};
+