Start work on rendering window borders
This commit is contained in:
parent
655ec36091
commit
b1e39dac02
116
kitty/borders.py
Normal file
116
kitty/borders.py
Normal file
@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ctypes
|
||||
|
||||
from .constants import viewport_size
|
||||
from .fast_data_types import glUniform3fv, GL_TRIANGLE_STRIP, glDrawArrays
|
||||
from .layout import available_height
|
||||
from .utils import get_dpi
|
||||
from .shaders import ShaderProgram
|
||||
|
||||
|
||||
def as_color(c):
|
||||
return c[0] / 255, c[1] / 255, c[2] / 255
|
||||
|
||||
|
||||
def to_opengl(x, y):
|
||||
return -1 + 2 * x / viewport_size.width, 1 - 2 * y / viewport_size.height
|
||||
|
||||
|
||||
def as_rect(left, top, right, bottom, color=0):
|
||||
for (x, y) in ((left, bottom), (right, bottom), (right, top), (left, top)):
|
||||
x, y = to_opengl(x, y)
|
||||
yield x
|
||||
yield y
|
||||
yield color
|
||||
|
||||
|
||||
class BordersProgram(ShaderProgram):
|
||||
|
||||
def __init__(self):
|
||||
ShaderProgram.__init__(self, '''\
|
||||
uniform vec3 colors[3];
|
||||
in vec3 vertex;
|
||||
out vec3 color;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(vertex[0], vertex[1], 0, 1);
|
||||
color = colors[uint(vertex[2])];
|
||||
}
|
||||
''', '''\
|
||||
in vec3 color;
|
||||
out vec4 final_color;
|
||||
|
||||
void main() {
|
||||
final_color = vec4(color, 1);
|
||||
}
|
||||
''')
|
||||
self.add_vertex_array('rects')
|
||||
|
||||
def send_data(self, data):
|
||||
self.send_vertex_data('rects', data)
|
||||
|
||||
def set_colors(self, color_buf):
|
||||
glUniform3fv(self.uniform_location('colors'), 3, ctypes.addressof(color_buf))
|
||||
|
||||
|
||||
class Borders:
|
||||
|
||||
def __init__(self, opts):
|
||||
self.is_dirty = False
|
||||
self.can_render = False
|
||||
dpix, dpiy = get_dpi()['logical']
|
||||
dpi = (dpix + dpiy) / 2
|
||||
self.border_width = round(opts.window_border_width * dpi / 72)
|
||||
self.color_buf = (ctypes.c_float * 9)(
|
||||
*as_color(opts.background),
|
||||
*as_color(opts.active_border_color),
|
||||
*as_color(opts.inactive_border_color)
|
||||
)
|
||||
|
||||
def __call__(self, windows, active_window, draw_window_borders=True):
|
||||
self.can_render = True
|
||||
self.is_dirty = True
|
||||
vw, vh = viewport_size.width, available_height()
|
||||
if windows:
|
||||
left_edge = min(w.geometry.left for w in windows)
|
||||
right_edge = max(w.geometry.right for w in windows)
|
||||
top_edge = min(w.geometry.top for w in windows)
|
||||
bottom_edge = max(w.geometry.bottom for w in windows)
|
||||
else:
|
||||
left_edge = top_edge = 0
|
||||
right_edge = vw
|
||||
bottom_edge = vh
|
||||
rects = []
|
||||
if left_edge > 0:
|
||||
rects.extend(as_rect(0, 0, left_edge, vh))
|
||||
if top_edge > 0:
|
||||
rects.extend(as_rect(0, 0, vw, top_edge))
|
||||
if right_edge < vw:
|
||||
rects.extend(as_rect(right_edge, 0, vw, vh))
|
||||
if bottom_edge < vh:
|
||||
rects.extend(as_rect(0, bottom_edge, vw, vh))
|
||||
if draw_window_borders and self.border_width > 0:
|
||||
bw = self.border_width
|
||||
for w in windows:
|
||||
g = w.geometry
|
||||
color = 1 if w is active_window else 2
|
||||
rects.extend(as_rect(g.left - bw, g.top - bw, g.left, g.bottom + bw, color))
|
||||
rects.extend(as_rect(g.left - bw, g.top - bw, g.right + bw, g.top, color))
|
||||
rects.extend(as_rect(g.right, g.top - bw, g.right + bw, g.bottom + bw, color))
|
||||
rects.extend(as_rect(g.left - bw, g.bottom, g.right + bw, g.bottom + bw, color))
|
||||
self.data = (ctypes.c_float * len(rects))()
|
||||
for i, x in enumerate(rects):
|
||||
self.data[i] = x
|
||||
|
||||
def render(self, program):
|
||||
if not self.can_render:
|
||||
return
|
||||
with program:
|
||||
if self.is_dirty:
|
||||
program.send_data(self.data)
|
||||
program.set_colors(self.color_buf)
|
||||
self.is_dirty = False
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
|
||||
@ -44,9 +44,10 @@ type_map = {
|
||||
'cursor_blink': to_bool,
|
||||
'cursor_opacity': to_opacity,
|
||||
'repaint_delay': int,
|
||||
'window_border_width': float,
|
||||
}
|
||||
|
||||
for name in 'foreground background cursor'.split():
|
||||
for name in 'foreground background cursor active_border_color inactive_border_color'.split():
|
||||
type_map[name] = lambda x: to_color(x, validate=True)
|
||||
for i in range(16):
|
||||
type_map['color%d' % i] = lambda x: to_color(x, validate=True)
|
||||
|
||||
65
kitty/gl.h
65
kitty/gl.h
@ -101,6 +101,17 @@ Uniform4f(PyObject UNUSED *self, PyObject *args) {
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
Uniform3fv(PyObject UNUSED *self, PyObject *args) {
|
||||
int location;
|
||||
unsigned int count;
|
||||
PyObject *l;
|
||||
if (!PyArg_ParseTuple(args, "iIO!", &location, &count, &PyLong_Type, &l)) return NULL;
|
||||
glUniform3fv(location, count, PyLong_AsVoidPtr(l));
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
// }}}
|
||||
|
||||
|
||||
@ -152,6 +163,17 @@ DrawArrays(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
MultiDrawArrays(PyObject UNUSED *self, PyObject *args) {
|
||||
int mode;
|
||||
unsigned int draw_count;
|
||||
PyObject *a, *b;
|
||||
if (!PyArg_ParseTuple(args, "iO!O!I", &mode, &PyLong_Type, &a, &PyLong_Type, &b, &draw_count)) return NULL;
|
||||
glMultiDrawArrays(mode, PyLong_AsVoidPtr(a), PyLong_AsVoidPtr(b), draw_count);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
DrawArraysInstanced(PyObject UNUSED *self, PyObject *args) {
|
||||
int mode, first;
|
||||
@ -431,14 +453,14 @@ TexSubImage3D(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
BufferData(PyObject UNUSED *self, PyObject *args) {
|
||||
int target, usage;
|
||||
unsigned long size;
|
||||
NamedBufferData(PyObject UNUSED *self, PyObject *args) {
|
||||
int usage;
|
||||
unsigned long size, target;
|
||||
PyObject *address;
|
||||
if (!PyArg_ParseTuple(args, "ikO!i", &target, &size, &PyLong_Type, &address, &usage)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "kkO!i", &target, &size, &PyLong_Type, &address, &usage)) return NULL;
|
||||
void *data = PyLong_AsVoidPtr(address);
|
||||
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
|
||||
glBufferData(target, size, data, usage);
|
||||
glNamedBufferData(target, size, data, usage);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@ -520,13 +542,34 @@ Disable(PyObject UNUSED *self, PyObject *val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
EnableVertexAttribArray(PyObject UNUSED *self, PyObject *val) {
|
||||
long x = PyLong_AsLong(val);
|
||||
glEnableVertexAttribArray(x);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
VertexAttribPointer(PyObject UNUSED *self, PyObject *args) {
|
||||
unsigned int index, stride;
|
||||
int type=GL_FLOAT, normalized, size;
|
||||
void *offset;
|
||||
PyObject *l;
|
||||
if (!PyArg_ParseTuple(args, "IiipIO!", &index, &size, &type, &normalized, &stride, &PyLong_Type, &l)) return NULL;
|
||||
offset = PyLong_AsVoidPtr(l);
|
||||
glVertexAttribPointer(index, size, type, normalized, stride, offset);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
int add_module_gl_constants(PyObject *module) {
|
||||
#define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; }
|
||||
GLC(GL_VERSION);
|
||||
GLC(GL_VENDOR);
|
||||
GLC(GL_SHADING_LANGUAGE_VERSION);
|
||||
GLC(GL_RENDERER);
|
||||
GLC(GL_TRIANGLE_FAN);
|
||||
GLC(GL_TRIANGLE_FAN); GLC(GL_TRIANGLE_STRIP);
|
||||
GLC(GL_COLOR_BUFFER_BIT);
|
||||
GLC(GL_VERTEX_SHADER);
|
||||
GLC(GL_FRAGMENT_SHADER);
|
||||
@ -543,9 +586,9 @@ int add_module_gl_constants(PyObject *module) {
|
||||
GLC(GL_TEXTURE_WRAP_S); GLC(GL_TEXTURE_WRAP_T);
|
||||
GLC(GL_UNPACK_ALIGNMENT);
|
||||
GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_RGB32UI);
|
||||
GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW);
|
||||
GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW); GLC(GL_STREAM_DRAW);
|
||||
GLC(GL_SRC_ALPHA); GLC(GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLC(GL_BLEND);
|
||||
GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_ARRAY_BUFFER);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -562,6 +605,7 @@ int add_module_gl_constants(PyObject *module) {
|
||||
METH(Uniform1i, METH_VARARGS) \
|
||||
METH(Uniform2f, METH_VARARGS) \
|
||||
METH(Uniform4f, METH_VARARGS) \
|
||||
METH(Uniform3fv, METH_VARARGS) \
|
||||
METH(GetUniformLocation, METH_VARARGS) \
|
||||
METH(GetAttribLocation, METH_VARARGS) \
|
||||
METH(ShaderSource, METH_VARARGS) \
|
||||
@ -582,11 +626,14 @@ int add_module_gl_constants(PyObject *module) {
|
||||
METH(DeleteShader, METH_O) \
|
||||
METH(Enable, METH_O) \
|
||||
METH(Disable, METH_O) \
|
||||
METH(EnableVertexAttribArray, METH_O) \
|
||||
METH(VertexAttribPointer, METH_VARARGS) \
|
||||
METH(GetProgramInfoLog, METH_O) \
|
||||
METH(GetShaderInfoLog, METH_O) \
|
||||
METH(ActiveTexture, METH_O) \
|
||||
METH(DrawArraysInstanced, METH_VARARGS) \
|
||||
METH(DrawArrays, METH_VARARGS) \
|
||||
METH(MultiDrawArrays, METH_VARARGS) \
|
||||
METH(CreateProgram, METH_NOARGS) \
|
||||
METH(AttachShader, METH_VARARGS) \
|
||||
METH(BindTexture, METH_VARARGS) \
|
||||
@ -597,6 +644,6 @@ int add_module_gl_constants(PyObject *module) {
|
||||
METH(TexStorage3D, METH_VARARGS) \
|
||||
METH(CopyImageSubData, METH_VARARGS) \
|
||||
METH(TexSubImage3D, METH_VARARGS) \
|
||||
METH(BufferData, METH_VARARGS) \
|
||||
METH(NamedBufferData, METH_VARARGS) \
|
||||
METH(BlendFunc, METH_VARARGS) \
|
||||
|
||||
|
||||
@ -31,6 +31,15 @@ scrollback_lines 2000
|
||||
# that sufficient for most uses.
|
||||
repaint_delay 20
|
||||
|
||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
window_border_width 2
|
||||
|
||||
# The color for the border of the active window
|
||||
active_border_color #00ff00
|
||||
|
||||
# The color for the border of inactive windows
|
||||
inactive_border_color #cccccc
|
||||
|
||||
# black
|
||||
color0 #000000
|
||||
color8 #4d4d4d
|
||||
|
||||
@ -2,7 +2,11 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from .constants import WindowGeometry, viewport_size, cell_size
|
||||
from .constants import WindowGeometry, viewport_size, cell_size, tab_manager
|
||||
|
||||
|
||||
def available_height():
|
||||
return viewport_size.height - tab_manager().current_tab_bar_height
|
||||
|
||||
|
||||
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0):
|
||||
@ -25,6 +29,11 @@ def layout_dimension(length, cell_length, number_of_windows=1, border_length=0):
|
||||
class Layout:
|
||||
|
||||
name = None
|
||||
needs_window_borders = True
|
||||
|
||||
def __init__(self, opts, border_width):
|
||||
self.opts = opts
|
||||
self.border_width = border_width
|
||||
|
||||
def add_window(self, windows, window):
|
||||
raise NotImplementedError()
|
||||
@ -36,9 +45,10 @@ class Layout:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Stack:
|
||||
class Stack(Layout):
|
||||
|
||||
name = 'stack'
|
||||
needs_window_borders = False
|
||||
|
||||
def add_window(self, windows, window):
|
||||
windows.appendleft(window)
|
||||
@ -50,7 +60,7 @@ class Stack:
|
||||
|
||||
def __call__(self, windows):
|
||||
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width))
|
||||
ystart, ynum = next(layout_dimension(viewport_size.height, cell_size.height))
|
||||
ystart, ynum = next(layout_dimension(available_height(), cell_size.height))
|
||||
wg = WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum)
|
||||
for i, w in enumerate(windows):
|
||||
w.is_visible_in_layout = i == 0
|
||||
|
||||
@ -14,14 +14,16 @@ from .fast_data_types import (
|
||||
glCreateShader, glShaderSource, glCompileShader, glGetShaderiv,
|
||||
GL_COMPILE_STATUS, glGetShaderInfoLog, glGetUniformLocation,
|
||||
glGetAttribLocation, glUseProgram, glBindVertexArray, GL_TEXTURE0,
|
||||
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glBufferData,
|
||||
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glNamedBufferData,
|
||||
GL_MAX_TEXTURE_SIZE, glDeleteTexture, GL_TEXTURE_2D_ARRAY, glGenTextures,
|
||||
glBindTexture, glTexParameteri, GL_CLAMP_TO_EDGE, glDeleteBuffer,
|
||||
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S,
|
||||
GL_NEAREST, GL_TEXTURE_WRAP_T, glGenBuffers, GL_R8, GL_RED,
|
||||
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_TEXTURE_BUFFER,
|
||||
GL_RGB32UI, glBindBuffer, glPixelStorei, glTexBuffer, glActiveTexture,
|
||||
glTexStorage3D, glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap
|
||||
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_STREAM_DRAW,
|
||||
GL_TEXTURE_BUFFER, GL_RGB32UI, GL_FLOAT, GL_ARRAY_BUFFER, glBindBuffer,
|
||||
glPixelStorei, glTexBuffer, glActiveTexture, glTexStorage3D,
|
||||
glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap,
|
||||
glEnableVertexAttribArray, glVertexAttribPointer
|
||||
)
|
||||
|
||||
GL_VERSION = (3, 3)
|
||||
@ -143,11 +145,8 @@ class Sprites:
|
||||
self.buffer_texture_id = glGenTextures(1)
|
||||
self.buffer_texture_unit = GL_TEXTURE1
|
||||
|
||||
def set_sprite_map(self, data):
|
||||
tgt = GL_TEXTURE_BUFFER
|
||||
glBindBuffer(tgt, self.buffer_id)
|
||||
glBufferData(tgt, sizeof(data), addressof(data), GL_STATIC_DRAW)
|
||||
glBindBuffer(tgt, 0)
|
||||
def set_sprite_map(self, data, usage=GL_STREAM_DRAW):
|
||||
glNamedBufferData(self.buffer_id, sizeof(data), addressof(data), usage)
|
||||
|
||||
def __enter__(self):
|
||||
self.ensure_state()
|
||||
@ -175,7 +174,6 @@ class ShaderProgram:
|
||||
|
||||
"""
|
||||
self.program_id = glCreateProgram()
|
||||
self.is_active = False
|
||||
vs_id = self.add_shader(vertex, GL_VERTEX_SHADER)
|
||||
glAttachShader(self.program_id, vs_id)
|
||||
|
||||
@ -192,6 +190,22 @@ class ShaderProgram:
|
||||
glDeleteShader(vs_id)
|
||||
glDeleteShader(frag_id)
|
||||
self.vao_id = glGenVertexArrays(1)
|
||||
self.buffers = {}
|
||||
|
||||
def add_vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0):
|
||||
glBindVertexArray(self.vao_id)
|
||||
if name not in self.buffers:
|
||||
self.buffers[name] = glGenBuffers(1)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, self.buffers[name])
|
||||
aid = self.attribute_location(name)
|
||||
glEnableVertexAttribArray(aid)
|
||||
glVertexAttribPointer(aid, size, dtype, normalized, stride, offset)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
glBindVertexArray(0)
|
||||
|
||||
def send_vertex_data(self, name, data, usage=GL_STATIC_DRAW):
|
||||
bufid = self.buffers[name]
|
||||
glNamedBufferData(bufid, sizeof(data), addressof(data), usage)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return self.program_id
|
||||
@ -230,9 +244,7 @@ class ShaderProgram:
|
||||
def __enter__(self):
|
||||
glUseProgram(self.program_id)
|
||||
glBindVertexArray(self.vao_id)
|
||||
self.is_active = True
|
||||
|
||||
def __exit__(self, *args):
|
||||
glUseProgram(0)
|
||||
glBindVertexArray(0)
|
||||
self.is_active = False
|
||||
|
||||
@ -20,6 +20,7 @@ from .child import Child
|
||||
from .constants import viewport_size, shell_path, appname, set_tab_manager, tab_manager, wakeup, cell_size
|
||||
from .fast_data_types import glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, glClearColor, glClear, GL_COLOR_BUFFER_BIT
|
||||
from .fonts import set_font_family
|
||||
from .borders import Borders, BordersProgram
|
||||
from .char_grid import cursor_shader, cell_shader
|
||||
from .keys import interpret_text_event, interpret_key_event, get_shortcut
|
||||
from .layout import Stack
|
||||
@ -36,7 +37,8 @@ class Tab:
|
||||
def __init__(self, opts, args):
|
||||
self.opts, self.args = opts, args
|
||||
self.windows = deque()
|
||||
self.current_layout = Stack()
|
||||
self.borders = Borders(opts)
|
||||
self.current_layout = Stack(opts, self.borders.border_width)
|
||||
|
||||
@property
|
||||
def is_visible(self):
|
||||
@ -58,6 +60,7 @@ class Tab:
|
||||
def relayout(self):
|
||||
if self.windows:
|
||||
self.current_layout(self.windows)
|
||||
self.borders(self.windows, self.active_window, self.current_layout.needs_window_borders)
|
||||
|
||||
def launch_child(self, use_shell=False):
|
||||
if use_shell:
|
||||
@ -92,8 +95,7 @@ class Tab:
|
||||
del self.windows
|
||||
|
||||
def render(self):
|
||||
# TODO: Render window borders and clear the extra pixels
|
||||
pass
|
||||
self.borders.render(tab_manager().borders_program)
|
||||
|
||||
|
||||
class TabManager(Thread):
|
||||
@ -103,6 +105,7 @@ class TabManager(Thread):
|
||||
def __init__(self, glfw_window, opts, args):
|
||||
Thread.__init__(self, name='ChildMonitor')
|
||||
self.glfw_window_title = None
|
||||
self.current_tab_bar_height = 0
|
||||
self.action_queue = Queue()
|
||||
self.pending_resize = None
|
||||
self.resize_gl_viewport = False
|
||||
@ -128,12 +131,13 @@ class TabManager(Thread):
|
||||
self.sprites = Sprites()
|
||||
self.cell_program = ShaderProgram(*cell_shader)
|
||||
self.cursor_program = ShaderProgram(*cursor_shader)
|
||||
self.borders_program = BordersProgram()
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
self.queue_action(self.active_tab.new_window, False)
|
||||
bg = opts.background
|
||||
glClearColor(bg.red / 255, bg.green / 255, bg.blue / 255, 1)
|
||||
glClear(GL_COLOR_BUFFER_BIT)
|
||||
self.queue_action(self.active_tab.new_window, False)
|
||||
|
||||
def signal_received(self):
|
||||
try:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user