Add SDL driver prototype. Update sdlterm: Handle keyboard, mouse events. Add glyph cache.
authorRadek Brich <radek.brich@devl.cz>
Sat, 05 Jan 2013 12:40:32 +0100
changeset 48 1f00e90fd72a
parent 47 537d7c6b48a2
child 49 1611c462c3e3
Add SDL driver prototype. Update sdlterm: Handle keyboard, mouse events. Add glyph cache.
sdlterm/cython/sdlterm.pyx
sdlterm/demo.cc
sdlterm/demo.py
sdlterm/src/sdlterm.cc
sdlterm/src/sdlterm.h
tuikit/driver_dummy.py
tuikit/driver_sdl.py
--- a/sdlterm/cython/sdlterm.pyx	Sat Jan 05 00:40:27 2013 +0100
+++ b/sdlterm/cython/sdlterm.pyx	Sat Jan 05 12:40:32 2013 +0100
@@ -10,11 +10,27 @@
 
 
 cdef extern from "sdlterm.h":
-    enum EventType:
-        pass
+    cdef struct Event_key:
+        char *keyname
+        Py_UNICODE unicode
+
+    cdef struct Event_mouse:
+        int x, y
+        int button
 
-    union Event:
-        EventType type
+    cdef struct Event:
+        # enum
+        int QUIT
+        int RESIZE
+        int KEYPRESS
+        int MOUSEDOWN
+        int MOUSEUP
+        int MOUSEMOVE
+        int MOUSEWHEEL
+
+        int type
+        Event_key key
+        Event_mouse mouse
 
     cdef cppclass Terminal:
         Terminal() except +
@@ -34,6 +50,9 @@
 
         void get_next_event(Event event)
 
+        int get_width()
+        int get_height()
+
 
 cdef class SDLTerminal:
     cdef Terminal *thisptr      # hold a C++ instance which we're wrapping
@@ -60,5 +79,28 @@
     def get_next_event(self):
         cdef Event event
         self.thisptr.get_next_event(event)
-        return dict()
+        if event.type == event.MOUSEMOVE:
+            return ('mousemove', event.mouse.x, event.mouse.y)
+        if event.type == event.MOUSEDOWN:
+            return ('mousedown', event.mouse.x, event.mouse.y, event.mouse.button)
+        if event.type == event.MOUSEUP:
+            return ('mouseup', event.mouse.x, event.mouse.y, event.mouse.button)
+        if event.type == event.KEYPRESS:
+            keyname = event.key.keyname
+            if keyname:
+                keyname = keyname.decode()
+            else:
+                keyname = None
+            char = event.key.unicode
+            if char == '\x00':
+                char = None
+            return ('keypress', keyname, char)
+        if event.type == event.QUIT:
+            return ('quit',)
+        return ('unknown',)
 
+    property width:
+        def __get__(self): return self.thisptr.get_width()
+    property height:
+        def __get__(self): return self.thisptr.get_height()
+
--- a/sdlterm/demo.cc	Sat Jan 05 00:40:27 2013 +0100
+++ b/sdlterm/demo.cc	Sat Jan 05 12:40:32 2013 +0100
@@ -31,7 +31,7 @@
 
 	switch (event.type)
 	{
-		case EventType::quit:
+		case Event::QUIT:
 			done = true;
 			break;
 
--- a/sdlterm/demo.py	Sat Jan 05 00:40:27 2013 +0100
+++ b/sdlterm/demo.py	Sat Jan 05 12:40:32 2013 +0100
@@ -16,5 +16,7 @@
 
     while True:
         event = term.get_next_event()
-        break
+        print(event)
+        if event[0] == 'keypress' and event[1] == 'escape':
+            break
 
--- a/sdlterm/src/sdlterm.cc	Sat Jan 05 00:40:27 2013 +0100
+++ b/sdlterm/src/sdlterm.cc	Sat Jan 05 12:40:32 2013 +0100
@@ -3,6 +3,33 @@
 #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)
 {
@@ -16,6 +43,7 @@
 
 GlyphRenderer::~GlyphRenderer()
 {
+	_cache.flush();
 	close_font();
 	TTF_Quit();
 }
@@ -84,6 +112,15 @@
 
 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')
@@ -93,7 +130,7 @@
 	}
 
 	// create surface for whole cell and fill it with bg color
-	SDL_Surface *cell_surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
+	cell_surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
 			_cell_width, _cell_height, 32, 0, 0, 0, 0);
 	SDL_Rect dst_rect;
 	dst_rect.x = 0;
@@ -112,6 +149,7 @@
 	SDL_BlitSurface(glyph_surface, NULL, cell_surface, &dst_rect);
 	SDL_FreeSurface(glyph_surface);
 
+	_cache.put_glyph(ch, cell_surface);
 	return cell_surface;
 }
 
@@ -133,7 +171,7 @@
 
 void TerminalScreen::resize(int pxwidth, int pxheight)
 {
-    _screen_surface = SDL_SetVideoMode(pxwidth, pxheight, 8, SDL_SWSURFACE|SDL_ANYFORMAT|SDL_RESIZABLE);
+    _screen_surface = SDL_SetVideoMode(pxwidth, pxheight, 32, SDL_HWSURFACE|SDL_RESIZABLE);
 
     if (_screen_surface == NULL)
     {
@@ -183,7 +221,6 @@
 			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++;
 		}
 	}
@@ -218,6 +255,7 @@
 		fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
 		throw std::exception();
     }
+    SDL_EnableUNICODE(1);
 }
 
 
@@ -236,12 +274,28 @@
 		switch (sdl_event.type)
 		{
 			case SDL_QUIT:
-				event.type = EventType::quit;
+				event.type = Event::QUIT;
 				return;
 
 			case SDL_KEYDOWN:
 				//switch(event.key.keysym.sym)
-				event.type = EventType::keypress;
+				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:
@@ -249,3 +303,41 @@
 		}
 	}
 }
+
+
+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 "";
+	}
+}
+
--- a/sdlterm/src/sdlterm.h	Sat Jan 05 00:40:27 2013 +0100
+++ b/sdlterm/src/sdlterm.h	Sat Jan 05 12:40:32 2013 +0100
@@ -5,6 +5,18 @@
 #include <vector>
 
 
+class GlyphCache
+{
+public:
+	SDL_Surface *lookup_glyph(Uint16 ch);
+	void put_glyph(Uint16 ch, SDL_Surface *srf);
+	void flush();
+
+private:
+	std::map<Uint16, SDL_Surface*> _glyph_map;
+};
+
+
 class GlyphRenderer
 {
 public:
@@ -14,6 +26,7 @@
 	void open_font(const char *fname_regular, const char *fname_bold, int ptsize);
 	void close_font();
 
+	// do not free surface returned!
 	SDL_Surface *render_glyph(Uint16 ch);
 
 	int get_cell_width() { return _cell_width; };
@@ -24,7 +37,7 @@
 	TTF_Font *_font_bold;
 	int _cell_width;
 	int _cell_height;
-	std::map<Uint16, SDL_Surface*> _cache;
+	GlyphCache _cache;
 };
 
 
@@ -48,6 +61,11 @@
 	void putch(int x, int y, Uint16 ch, Uint16 attr);
 	void commit();
 
+	int get_width() { return _width; };
+	int get_height() { return _height; };
+	int get_cell_width() { return _cell_width; };
+	int get_cell_height() { return _cell_height; };
+
 private:
 	SDL_Surface *_screen_surface;
 	TerminalCell *_cells;
@@ -64,42 +82,24 @@
 };
 
 
-enum class EventType : Uint8
+struct Event
 {
-	quit,
-	resize,
-	keypress,
-	mousedown,
-	mouseup,
-	mousemove,
-	mousewheel
-};
-
-
-struct WindowEvent
-{
-	EventType type;
-};
-
+	enum { QUIT, RESIZE, KEYPRESS, MOUSEDOWN, MOUSEUP, MOUSEMOVE, MOUSEWHEEL };
+	int type;
 
-struct KeyboardEvent
-{
-	EventType type;
-};
-
-
-struct MouseEvent
-{
-	EventType type;
-};
-
-
-union Event
-{
-	EventType type;
-	WindowEvent window;
-	KeyboardEvent key;
-	MouseEvent mouse;
+	union
+	{
+		struct
+		{
+			char keyname[10];
+			Uint16 unicode;
+		} key;
+		struct
+		{
+			int x, y;
+			int button;
+		} mouse;
+	};
 };
 
 
@@ -125,11 +125,16 @@
 
 	void get_next_event(Event &event);
 
+	int get_width() { return _screen.get_width(); };
+	int get_height() { return _screen.get_height(); };
+
 private:
 	TerminalScreen _screen;
 	Uint16 _attr;
 	int _cursor_x;
 	int _cursor_y;
 	bool _cursor_visible;
+
+	const char *_translate_keyname(SDLKey sym);
 };
 
--- a/tuikit/driver_dummy.py	Sat Jan 05 00:40:27 2013 +0100
+++ b/tuikit/driver_dummy.py	Sat Jan 05 12:40:32 2013 +0100
@@ -12,40 +12,42 @@
 
 
 class DriverDummy(Driver):
-    
+
     '''Dummy driver class'''
-    
+
     def __init__(self):
         '''Initialize instance attributes'''
         Driver.__init__(self)
         self.log = logging.getLogger('tuikit')
         self.size.w, self.size.h = 80, 25
-    
+
     def start(self, mainfunc):
         '''Start driver and run mainfunc.'''
         mainfunc()
 
 
     ## input ##
-    
+
     def getevents(self, timeout=None):
         '''Process input, return list of events.
-        
+
         This dummy implementation just returns 'q' and Escape key presses.
-        
+
         '''
         events = [('keypress', None, 'q'), ('keypress', 'escape', None)]
         return events
 
 
     ## drawing ##
-    
+
     def erase(self):
         '''Clear screen.'''
         self.log.info('DummyDriver.erase()')
 
     def putch(self, x, y, c):
         '''Output one unicode character to specified coordinates.'''
+        if not self.clipstack.test(x, y):
+            return
         self.log.info('DummyDriver.putch(x=%r, y=%r, c=%r)', x, y, c)
 
     def commit(self):
@@ -54,20 +56,20 @@
 
 
     ## colors ##
-    
+
     def setcolor(self, name, desc):
         '''Define color name.
-        
+
         name - name of color (e.g. 'normal', 'active')
         desc - color description - foreground, background, attributes (e.g. 'black on white, bold')
-        
+
         '''
         self.log.info('DummyDriver.setcolor(name=%r, desc=%r)', name, desc)
 
     def pushcolor(self, name):
         '''Add color on top of stack and use this color for following output.'''
         self.log.info('DummyDriver.pushcolor(name=%r)', name)
-    
+
     def popcolor(self):
         '''Remove color from top of stack and use new top color for following output.'''
         self.log.info('DummyDriver.popcolor()')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/driver_sdl.py	Sat Jan 05 12:40:32 2013 +0100
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+'''SDL driver.
+
+Requires C++ extension module.
+See sdlterm directory.
+
+'''
+
+from tuikit.driver import Driver
+
+import logging
+
+import sys
+sys.path.insert(0, 'sdlterm/build/lib.linux-x86_64-3.2')
+from sdlterm import SDLTerminal
+
+
+class DriverSDL(Driver):
+
+    '''SDL driver class'''
+
+    def __init__(self):
+        '''Initialize instance attributes'''
+        Driver.__init__(self)
+        self.log = logging.getLogger('tuikit')
+        self.sdlterm = SDLTerminal()
+
+    def start(self, mainfunc):
+        '''Start driver and run mainfunc.'''
+        self.sdlterm.resize(800, 600)
+        self.sdlterm.select_font("sdlterm/font/DejaVuSansMono.ttf", "sdlterm/font/DejaVuSansMono-Bold.ttf", 12)
+        self.size.w, self.size.h = self.sdlterm.width, self.sdlterm.height
+        mainfunc()
+
+
+    ## input ##
+
+    def getevents(self, timeout=None):
+        '''Process input, return list of events.'''
+        event = self.sdlterm.get_next_event()
+        return [event]
+
+
+    ## drawing ##
+
+    def erase(self):
+        '''Clear screen.'''
+        self.sdlterm.erase()
+
+    def putch(self, x, y, c):
+        '''Output one unicode character to specified coordinates.'''
+        if not self.clipstack.test(x, y):
+            return
+        self.sdlterm.putch(x, y, c)
+
+    def commit(self):
+        '''Commit changes to the screen.'''
+        self.sdlterm.commit()
+
+
+    ## colors ##
+
+    def setcolor(self, name, desc):
+        '''Define color name.
+
+        name - name of color (e.g. 'normal', 'active')
+        desc - color description - foreground, background, attributes (e.g. 'black on white, bold')
+
+        '''
+        self.log.info('DummyDriver.setcolor(name=%r, desc=%r)', name, desc)
+
+    def pushcolor(self, name):
+        '''Add color on top of stack and use this color for following output.'''
+        self.log.info('DummyDriver.pushcolor(name=%r)', name)
+
+    def popcolor(self):
+        '''Remove color from top of stack and use new top color for following output.'''
+        self.log.info('DummyDriver.popcolor()')
+
+
+    ## cursor ##
+
+    def showcursor(self, x, y):
+        '''Set cursor to be shown at x, y coordinates.'''
+        self.log.info('DummyDriver.showcursor(x=%r, y=%r)', x, y)
+
+    def hidecursor(self):
+        '''Hide cursor.'''
+        self.log.info('DummyDriver.hidecursor()')
+
+
+driverclass = DriverSDL
+