You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
519 lines
13 KiB
519 lines
13 KiB
// Copyright (c) Ethan Eade, https://bitbucket.org/ethaneade/glwindow
|
|
|
|
#include "glwindow.hpp"
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/keysym.h>
|
|
#include <GL/glx.h>
|
|
#include <iostream>
|
|
|
|
using namespace glwindow;
|
|
|
|
std::vector<GLWindow*> GLWindow::all_windows;
|
|
|
|
void GLWindow::add_window(GLWindow *win)
|
|
{
|
|
all_windows.push_back(win);
|
|
}
|
|
|
|
bool GLWindow::remove_window(GLWindow *win)
|
|
{
|
|
for (size_t i=0; i<all_windows.size(); ++i) {
|
|
if (all_windows[i] == win) {
|
|
all_windows[i] = all_windows.back();
|
|
all_windows.pop_back();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void GLWindow::handle_all_events()
|
|
{
|
|
for (size_t i=0; i<all_windows.size(); ++i)
|
|
all_windows[i]->handle_events();
|
|
}
|
|
|
|
GLWindow *GLWindow::active_context = 0;
|
|
|
|
bool GLWindow::push_context()
|
|
{
|
|
prev_active = active_context;
|
|
return make_current();
|
|
}
|
|
|
|
void GLWindow::pop_context()
|
|
{
|
|
if (active_context != this)
|
|
return;
|
|
|
|
if (prev_active) {
|
|
prev_active->make_current();
|
|
} else {
|
|
active_context = 0;
|
|
}
|
|
}
|
|
|
|
void GLWindow::add_handler(EventHandler* handler)
|
|
{
|
|
handlers.push_back(handler);
|
|
}
|
|
|
|
bool GLWindow::remove_handler(EventHandler* handler)
|
|
{
|
|
std::vector<EventHandler*>::reverse_iterator it;
|
|
for (it = handlers.rbegin(); it != handlers.rend(); ++it) {
|
|
if (*it == handler) {
|
|
handlers.erase(it.base());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_key_down(GLWindow& win, int key) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_key_down(win, key))
|
|
return true;
|
|
return false;
|
|
}
|
|
bool EventDispatcher::on_key_up(GLWindow& win, int key) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_key_up(win, key))
|
|
return true;
|
|
return false;
|
|
}
|
|
bool EventDispatcher::on_text(GLWindow& win, const char *text, int len) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_text(win, text, len))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_button_down(GLWindow& win, int btn, int state, int x, int y) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_button_down(win, btn, state, x, y))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_button_up(GLWindow& win, int btn, int state, int x, int y) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_button_up(win, btn, state, x, y))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_mouse_move(GLWindow& win, int state, int x, int y) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_mouse_move(win, state, x, y))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_mouse_wheel(GLWindow& win, int state, int x, int y, int dx, int dy) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_mouse_wheel(win, state, x, y, dx, dy))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_resize(GLWindow &win, int x, int y, int w, int h) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_resize(win, x, y, w, h))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool EventDispatcher::on_close(GLWindow &win) {
|
|
for (int i=handlers.size()-1; i>=0; --i)
|
|
if (handlers[i]->on_close(win))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static XVisualInfo *makeVisualInfo(Display *display)
|
|
{
|
|
int visualAttributes[] = {
|
|
GLX_RED_SIZE, 8,
|
|
GLX_GREEN_SIZE, 8,
|
|
GLX_BLUE_SIZE, 8,
|
|
GLX_DEPTH_SIZE, 16,
|
|
GLX_STENCIL_SIZE, 8,
|
|
GLX_RGBA,
|
|
GLX_DOUBLEBUFFER,
|
|
None
|
|
};
|
|
XVisualInfo *vi = glXChooseVisual(display, DefaultScreen(display), visualAttributes);
|
|
return vi;
|
|
}
|
|
|
|
static Window makeWindow(Display *display, XVisualInfo *vi, int width, int height)
|
|
{
|
|
Window rootWindow = RootWindow(display, vi->screen);
|
|
|
|
XSetWindowAttributes attributes;
|
|
attributes.border_pixel = 0;
|
|
attributes.colormap = XCreateColormap(display, rootWindow, vi->visual, AllocNone);
|
|
attributes.event_mask = (KeyPressMask | KeyReleaseMask |
|
|
ButtonPressMask | ButtonReleaseMask |
|
|
PointerMotionMask |
|
|
VisibilityChangeMask |
|
|
StructureNotifyMask |
|
|
ExposureMask);
|
|
|
|
Window window = XCreateWindow(display,
|
|
rootWindow,
|
|
0, 0, width, height,
|
|
0, vi->depth,
|
|
InputOutput,
|
|
vi->visual,
|
|
CWBorderPixel | CWColormap | CWEventMask,
|
|
&attributes);
|
|
return window;
|
|
}
|
|
|
|
struct GLWindow::SystemState
|
|
{
|
|
Display* display;
|
|
Window window;
|
|
GLXContext context;
|
|
Atom delete_atom;
|
|
Cursor cursor;
|
|
|
|
int width, height;
|
|
bool visible;
|
|
|
|
SystemState() {
|
|
display = 0;
|
|
window = 0;
|
|
width = 0;
|
|
height = 0;
|
|
visible = false;
|
|
}
|
|
~SystemState() {
|
|
if (!display)
|
|
return;
|
|
|
|
if (context) {
|
|
destroy();
|
|
|
|
glXMakeCurrent(display, None, 0);
|
|
glXDestroyContext(display, context);
|
|
}
|
|
|
|
XCloseDisplay(display);
|
|
}
|
|
|
|
bool init(int w, int h, const char *title)
|
|
{
|
|
display = XOpenDisplay(0);
|
|
if (!display)
|
|
return false;
|
|
XVisualInfo *vi = makeVisualInfo(display);
|
|
if (!vi)
|
|
return false;
|
|
|
|
context = glXCreateContext(display, vi, 0, True);
|
|
if (!context)
|
|
return false;
|
|
|
|
width = w;
|
|
height = h;
|
|
window = makeWindow(display, vi, width, height);
|
|
if (!window)
|
|
return false;
|
|
|
|
XStoreName(display, window, title);
|
|
|
|
{
|
|
XClassHint classHint;
|
|
classHint.res_name = const_cast<char*>(title);
|
|
char classname[] = "glwindow";
|
|
classHint.res_class = classname;
|
|
XSetClassHint(display, window, &classHint);
|
|
XMapWindow(display, window);
|
|
}
|
|
|
|
XEvent ev;
|
|
do {
|
|
XNextEvent(display, &ev);
|
|
} while (ev.type != MapNotify);
|
|
|
|
visible = true;
|
|
delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", True);
|
|
XSetWMProtocols(display, window, &delete_atom, 1);
|
|
|
|
cursor = XCreateFontCursor(display, ' ');
|
|
return true;
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
if (window) {
|
|
XUnmapWindow(display, window);
|
|
XDestroyWindow(display, window);
|
|
window = 0;
|
|
}
|
|
}
|
|
|
|
void swap_buffers()
|
|
{
|
|
if (window) {
|
|
glXSwapBuffers(display, window);
|
|
}
|
|
}
|
|
|
|
void set_title(const char *title)
|
|
{
|
|
if (window) {
|
|
XStoreName(display, window, title);
|
|
}
|
|
}
|
|
|
|
bool make_current()
|
|
{
|
|
if (!window)
|
|
return false;
|
|
glXMakeCurrent(display, window, context);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
GLWindow::GLWindow(int w, int h, const char *title)
|
|
{
|
|
sys_state = new SystemState();
|
|
sys_state->init(w, h, title);
|
|
all_windows.push_back(this);
|
|
}
|
|
|
|
GLWindow::~GLWindow()
|
|
{
|
|
for (size_t i=0; i<all_windows.size(); ++i) {
|
|
if (all_windows[i] == this) {
|
|
all_windows[i] = all_windows.back();
|
|
all_windows.pop_back();
|
|
break;
|
|
}
|
|
}
|
|
delete sys_state;
|
|
}
|
|
|
|
int GLWindow::width() const
|
|
{
|
|
return sys_state->width;
|
|
}
|
|
|
|
int GLWindow::height() const
|
|
{
|
|
return sys_state->height;
|
|
}
|
|
|
|
bool GLWindow::visible() const
|
|
{
|
|
return sys_state->visible;
|
|
}
|
|
|
|
bool GLWindow::alive() const
|
|
{
|
|
return 0 != sys_state->window;
|
|
}
|
|
|
|
bool GLWindow::make_current()
|
|
{
|
|
if (!sys_state->make_current())
|
|
return false;
|
|
|
|
active_context = this;
|
|
return true;
|
|
}
|
|
|
|
void GLWindow::swap_buffers()
|
|
{
|
|
sys_state->swap_buffers();
|
|
}
|
|
|
|
void GLWindow::set_size(int w, int h)
|
|
{
|
|
if (!alive())
|
|
return;
|
|
|
|
XWindowChanges c;
|
|
c.width = w;
|
|
c.height = h;
|
|
XConfigureWindow(sys_state->display,
|
|
sys_state->window,
|
|
CWWidth | CWHeight,
|
|
&c);
|
|
}
|
|
|
|
void GLWindow::set_position(int x, int y)
|
|
{
|
|
if (!alive())
|
|
return;
|
|
|
|
XWindowChanges c;
|
|
c.x = x;
|
|
c.y = y;
|
|
XConfigureWindow(sys_state->display,
|
|
sys_state->window,
|
|
CWX | CWY,
|
|
&c);
|
|
}
|
|
|
|
void GLWindow::set_title(const char* title)
|
|
{
|
|
if (!alive())
|
|
return;
|
|
|
|
sys_state->set_title(title);
|
|
}
|
|
|
|
static int convert_button_state(unsigned int state)
|
|
{
|
|
int s = 0;
|
|
if (state & Button1Mask) s |= ButtonEvent::LEFT;
|
|
if (state & Button2Mask) s |= ButtonEvent::MIDDLE;
|
|
if (state & Button3Mask) s |= ButtonEvent::RIGHT;
|
|
if (state & ControlMask) s |= ButtonEvent::MODKEY_CTRL;
|
|
if (state & ShiftMask) s |= ButtonEvent::MODKEY_SHIFT;
|
|
return s;
|
|
}
|
|
|
|
static int convert_button(int button)
|
|
{
|
|
switch (button) {
|
|
case Button1: return ButtonEvent::LEFT;
|
|
case Button2: return ButtonEvent::MIDDLE;
|
|
case Button3: return ButtonEvent::RIGHT;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
static int convert_keycode(int key)
|
|
{
|
|
switch (key) {
|
|
case XK_BackSpace: return KeyCode::BACKSPACE;
|
|
case XK_Tab: return KeyCode::TAB;
|
|
case XK_Return: return KeyCode::ENTER;
|
|
case XK_Shift_L: return KeyCode::SHIFT;
|
|
case XK_Shift_R: return KeyCode::SHIFT;
|
|
case XK_Control_L: return KeyCode::CTRL;
|
|
case XK_Control_R: return KeyCode::CTRL;
|
|
case XK_Alt_L: return KeyCode::ALT;
|
|
case XK_Alt_R: return KeyCode::ALT;
|
|
case XK_Super_L: return KeyCode::SUPER;
|
|
case XK_Super_R: return KeyCode::SUPER;
|
|
case XK_Caps_Lock: return KeyCode::CAPSLOCK;
|
|
case XK_Delete: return KeyCode::DEL;
|
|
case XK_Escape: return KeyCode::ESCAPE;
|
|
case XK_Left: return KeyCode::LEFT;
|
|
case XK_Up: return KeyCode::UP;
|
|
case XK_Right: return KeyCode::RIGHT;
|
|
case XK_Down: return KeyCode::DOWN;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
void GLWindow::handle_events()
|
|
{
|
|
if (!alive())
|
|
return;
|
|
|
|
XEvent event;
|
|
KeySym key;
|
|
const int text_size = 64;
|
|
char text[text_size];
|
|
int len;
|
|
EventDispatcher dispatcher(handlers);
|
|
|
|
int btn, state;
|
|
|
|
while (XPending(sys_state->display))
|
|
{
|
|
XNextEvent(sys_state->display, &event);
|
|
//std::cerr << "event " << event.type << std::endl;
|
|
switch (event.type) {
|
|
case ButtonPress:
|
|
state = convert_button_state(event.xbutton.state);
|
|
if (event.xbutton.button == Button4) {
|
|
// MouseWheel down
|
|
dispatcher.on_mouse_wheel(*this, state, event.xbutton.x, event.xbutton.y, 0, 1);
|
|
} else if (event.xbutton.button == Button5) {
|
|
// MouseWheel up
|
|
dispatcher.on_mouse_wheel(*this, state, event.xbutton.x, event.xbutton.y, 0, -1);
|
|
} else {
|
|
btn = convert_button(event.xbutton.button);
|
|
dispatcher.on_button_down(*this, btn, state, event.xbutton.x, event.xbutton.y);
|
|
}
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
if (event.xbutton.button == Button4 ||
|
|
event.xbutton.button == Button5)
|
|
break;
|
|
btn = convert_button(event.xbutton.button);
|
|
state = convert_button_state(event.xbutton.state);
|
|
dispatcher.on_button_up(*this, btn, state, event.xbutton.x, event.xbutton.y);
|
|
break;
|
|
|
|
case MotionNotify:
|
|
state = convert_button_state(event.xbutton.state);
|
|
dispatcher.on_mouse_move(*this, state, event.xmotion.x, event.xmotion.y);
|
|
break;
|
|
|
|
case KeyPress:
|
|
len = XLookupString(&event.xkey, text, text_size-1, &key, 0);
|
|
dispatcher.on_key_down(*this, convert_keycode(key));
|
|
if (len > 0) {
|
|
text[len] = 0;
|
|
dispatcher.on_text(*this, text, len);
|
|
}
|
|
break;
|
|
|
|
case KeyRelease:
|
|
XLookupString(&event.xkey, 0, 0, &key, 0);
|
|
dispatcher.on_key_up(*this, convert_keycode(key));
|
|
break;
|
|
|
|
case ConfigureNotify:
|
|
sys_state->width = event.xconfigure.width;
|
|
sys_state->height = event.xconfigure.height;
|
|
dispatcher.on_resize(*this, event.xconfigure.x, event.xconfigure.y,
|
|
sys_state->width, sys_state->height);
|
|
break;
|
|
|
|
case VisibilityNotify:
|
|
if (event.xvisibility.state == VisibilityFullyObscured)
|
|
sys_state->visible = false;
|
|
else
|
|
sys_state->visible = true;
|
|
break;
|
|
|
|
case DestroyNotify:
|
|
//std::cerr << "DestroyNotify" << std::endl;
|
|
//sys_state->window = 0;
|
|
break;
|
|
|
|
case Expose:
|
|
//std::cerr << "Expose" << std::endl;
|
|
break;
|
|
|
|
case ClientMessage:
|
|
if (event.xclient.data.l[0] == (int)sys_state->delete_atom) {
|
|
if (!dispatcher.on_close(*this))
|
|
destroy();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLWindow::destroy()
|
|
{
|
|
sys_state->destroy();
|
|
}
|