class FragmentGraph(DomainGraph): DEFAULT_FRAGMENT_KERNEL = """ vec4 fragment_kernel(vec2 txcoord) { return tovec4($D.domain(txcoord)); } """ FRAGMENT_KERNELS = { 'expr': lambda e: "vec4 fragment_kernel(vec2 txcoord) { \n\treturn " + e + "; \n}", } cs = attributes.VectorAttribute(4) # main domain name fragment_kernel = attributes.CastedAttribute(str) def __init__(self, domain=None, cs=None, fragment_kernel=None): super().__init__(domain) self.cs = cs if 'domain' in self.domains: self.fragment_kernel = fragment_kernel or self.DEFAULT_FRAGMENT_KERNEL elif fragment_kernel is None: raise ValueError( 'argument fragment_kernel cannot be None without default domain' ) else: self.fragment_kernel = fragment_kernel self.mesh = None self.program = None self._fkernel_template = None def init(self): self._build_kernel() self.program = self._build_shader() self.mesh = StridedVertexMesh( mesh3d_rectangle(), GL_TRIANGLES, attribute_locations=self.program.attributes) self.on_tick.once(self.sync_gpu) @cs.on_change def _properties_changed(self, *e): self.on_tick.once(self.sync_gpu) def _build_kernel(self): context = self.get_domain_glsl_substitutions() kernel = Template(self.fragment_kernel, context) kernel.context.update({'size': 'gl_PointSize', 'color': 'v_col'}) self._fkernel_template = kernel def _build_shader(self): prg = DomainProgram(vrt_file='fragmentgraph.vrt.glsl', frg_file='fragmentgraph.frg.glsl') prg.declare_uniform('camera', components.camera.Camera2D.DTYPE, variable='camera') prg.declare_uniform('plot', plotter2d.Plotter2d.UBO_DTYPE, variable='plot') prg.prepare_domains(self.domains) prg.get_shader(GL_FRAGMENT_SHADER).substitutions.update({ 'fragment_kernel': self._fkernel_template.render(), }) prg.link() prg.uniform_block_binding( 'plot', G_.CONTEXT.buffer_base('gpupy.plot.plotter2d')) prg.uniform_block_binding('camera', G_.CONTEXT.buffer_base('gpupy.gl.camera')) self._src = prg.get_shader(GL_FRAGMENT_SHADER)._precompiled_source return prg def sync_gpu(self): cs = self.cs.values self.program.uniform('cs', cs) self.program.uniform('cs_size', (np.abs(cs[1] - cs[0]), np.abs(cs[3] - cs[2]))) def render(self): # enable all domains domain.enable_domains(self.program, self.domains.items()) # blending glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # draw mesh self.program.use() self.mesh.draw() self.program.unuse() @fragment_kernel.transformation def set_color_kernel(self, ckrn): kernels = self.__class__.FRAGMENT_KERNELS ckrn_args, ckrn_kwargs = (), {} # we have ('kernel_name', ***) # i ('kernel_name', {kwargs}) --> kernel(**kwargs) # ii ('kernel_name', [args]) --> kernel(*args) # iii ('kernel_name', (args)) --> kernel(*args) # iv ('kernel_name', (args), {kwargs}) --> kernel(*args, **kwargs) # v ('kernel_name', x, y, z, ...) --> kernel(x, y, z, ...) if isinstance(ckrn, tuple) \ or isinstance(ckrn, list): if len(ckrn) == 2: # i - iii if isinstance(ckrn[1], dict): ckrn_kwargs = ckrn[1] elif isinstance(ckrn[1], tuple) \ or isinstance(ckrn[1], list): ckrn_args = ckrn[1] else: ckrn_args = (ckrn[1], ) elif len(ckrn) == 3 \ and isinstance(ckrn[2], dict) \ and (isinstance(ckrn[1], tuple) or isinstance(ckrn[1], list)): ckrn_args, ckrn_kwargs = ckrn[1:3] # iv elif len(ckrn) > 1: ckrn_args = ckrn[1:] # v ckrn = ckrn[0] # if ckrn is registred, use it. if ckrn in kernels: ckrn = kernels[ckrn] # is it callable? if hasattr(ckrn, '__call__'): ckrn = ckrn(*ckrn_args, **ckrn_kwargs) elif len(ckrn_args) or len(ckrn_kwargs): msg = 'kernel {} is not callable, but kernel args (), kwargs () are defined.' raise RuntimeError(msg.format(ckrn, ckrn_args, ckrn_kwargs)) return ckrn
class Cartesian2D(Camera): """ 2d cartesian camera Attributes: screensize (float[2]): lengths of the space dimensions position (float[3]): camera position roll (float): rotation of the coordinate space Examples: XXX """ screensize = attributes.VectorAttribute(2, (1, 1)) roll = attributes.CastedAttribute(float, 0) position = attributes.VectorAttribute(3, (0, 0, 0)) DTYPE = np.dtype([ ('mat_view', np.float32, (4, 4)), ('mat_projection', np.float32, (4, 4)), ('position', np.float32, 3), ('roll', np.float32), ('direction', np.float32, 3), ('_b1', np.float32), ('direction_right', np.float32, 3), ('_b2', np.float32), ('direction_top', np.float32, 3), ]) def __init__(self, screensize, position=(0, 0, 0), roll=0, buffer_base=None): super().__init__( Cartesian2D.DTYPE, buffer_base or GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self._camera = np.zeros(1, dtype=Cartesian2D.DTYPE) self.screensize = screensize self.position = position self.roll = roll self.commit() # -- api -- def __buffer__(self): mat_projection = mat4_rot_z(self.roll) @ np.array( [ 1, 0, 0, -self.position.x, 0, 1, 0, -self.position.y, 0, 0, 1, 0, 0, 0, 0, 1, ], dtype=np.float32).reshape((4, 4)) mat_view = np.array([ 2.0 / self.screensize.x, 0, 0, 0, 0, -2.0 / self.screensize.y, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ], dtype=np.float32).reshape((4, 4)) self._camera['mat_view'] = mat_view.T self._camera['mat_projection'] = mat_projection.T self._camera['roll'] = self.roll self._camera['position'] = self.position.xyz # XXX is a function of roll self._camera['direction'] = (0, 0, -1) self._camera['direction_right'] = (1, 0, 0) self._camera['direction_top'] = (0, 1, 0) return self._camera # -- matrix methods @roll.on_change @position.on_change @screensize.on_change def _attributes_changed(self, *e): self.commit()
class Context(): """ context API: events: ------- on_ready when the context is ready to let the OpenGL.GL functions do their job. on_cycle when the cycle logic should be executed on_resize when the context was resized. It is allowed to perform the tick and rendering logic like on_cycle to provide fluid window resizing. """ size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(2) title = attributes.CastedAttribute(str, 'window') visible = attributes.CastedAttribute(bool) def __init__(self): self.gl_buffer_base_register = {} self._next_free_gl_buffer_base_index = 0 self.on_ready = Event() self.on_cycle = Event() self.on_resize = Event() self.on_close = Event() self.active_keys = set() def buffer_base(self, name, index=None): """ reserves a buffer base with a **name** at **index**. if no **index** was given, the next free buffer base will be reserved. The reserved buffer index is then returned. Example: ```python from gpupy.gl import VertexBuffer, GPUPY_GL ubo = VertexBuffer.to_device(ndarray, target=UNIFORM_BUFFER) ubo.bind_buffer_base(GPUPY_GL.CONTEXT.buffer_base('some_name')) ``` """ i_max = glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS) if not name in self.gl_buffer_base_register: if index is not None and index in self.gl_buffer_base_register: raise RuntimeError('allready registred.') index = index or self._next_free_gl_buffer_base_index if index > i_max: raise OverflowError() self.gl_buffer_base_register[ name] = index or self._next_free_gl_buffer_base_index # find lowest free index ui = sorted(self.gl_buffer_base_register.values()) for i in range(len(ui) - 1): if ui[i + 1] - ui[i] != 1: self._next_free_gl_buffer_base_index = ui[i] + 1 return self._next_free_gl_buffer_base_index = len(ui) return self.gl_buffer_base_register[name] def __gl_context_enable__(self): """ activates OpenGL context """ GPUPY_GL.CONTEXT = self
class Perspective3D(Camera): """ 3d perspective camera Attributes: fov (float): field of view angle frustum (float[2]): near and far plane screensize (float[2]): coordinate space of near plane position (float[3]): camera position rotation (float[3]): roll, pitch, yaw Examples: XXX """ screensize = attributes.VectorAttribute(2, (1, 1)) rotation = attributes.VectorAttribute(3, (0, 0, 0)) position = attributes.VectorAttribute(3, (0, 0, 0)) frustum = attributes.VectorAttribute(2, (0.1, 10000)) fov = attributes.CastedAttribute(float, 0.5 * 65.0 * np.pi / 180.0) DTYPE = np.dtype([ ('mat_view', np.float32, (4, 4)), ('mat_projection', np.float32, (4, 4)), ('mat_viewprojection', np.float32, (4, 4)), ('position', np.float32, 3), ('yaw', np.float32), ('direction', np.float32, 3), ('pitch', np.float32), ('direction_right', np.float32, 3), ('roll', np.float32), ('direction_top', np.float32, 3), ]) def __init__(self, screensize, position=(0, 0, 0), rotation=(0, 0, 0), buffer_base=None): super().__init__( Perspective3D.DTYPE, buffer_base or GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self._camera = np.zeros(1, dtype=Perspective3D.DTYPE) self._mat_projection = None self._mat_view = None self.screensize = screensize self.position = position self.rotation = rotation self.direction = (0, 0, 0) self.right = (0, 0, 0) self.top = (0, 0, 0) self.commit() # -- api -- def __buffer__(self): # camera position and rotation matrix reflection_xy = mat4_reflection_xy() position_matrix = mat4_translation(*self.position) rot_roll = mat4_rot_z(self.rotation[2]) rot_yaw = mat4_rot_y(self.rotation[1]) rot_pitch = mat4_rot_x(self.rotation[0]) mat_view = ( rot_roll @ rot_pitch @ rot_yaw @ position_matrix @ reflection_xy) # perspective projection (n, f), ratio = self.frustum, self.screensize[0] / self.screensize[1] h = n * np.tan(self.fov) w = h * ratio mat_proj = np.array([ n / w, 0, 0, 0, 0, n / h, 0, 0, 0, 0, -(f + n) / (f - n), -2.0 * f * n / (f - n), 0, 0, -1, 0 ], dtype=np.float32).reshape((4, 4)) # ubo self._camera['mat_view'] = mat_view.T self._camera['mat_projection'] = mat_proj.T # remember.. (AB)^T = (B^T)(A^T) self._camera['mat_viewprojection'] = (mat_proj @ mat_view).T self._camera['roll'], \ self._camera['pitch'], \ self._camera['yaw'] = self.rotation self._camera['position'] = self.position self._camera['direction'] = self.direction self._camera['direction_right'] = self.right self._camera['direction_top'] = self.top return self._camera # -- camera control events -- @position.on_change @screensize.on_change @rotation.on_change def _attributes_changed(self, *e): self.commit()
class AbstractGrid(Widget): """ """ # widget configuration size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4) cs = attributes.VectorAttribute(4) # grid configuration major_grid = attributes.VectorAttribute(2, (.5, .5)) major_grid_width = attributes.CastedAttribute(float, 0.5) major_grid_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) minor_grid_width = attributes.CastedAttribute(float, .5) minor_grid_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) minor_grid_n = attributes.VectorAttribute(2, (5, 5)) antialiasing = attributes.CastedAttribute(float, .5) # style background_color = attributes.VectorAttribute(4, (1, 1, 1, 1)) def __init__(self, size, position=(0, 0, 0, 1), cs=(0, 0), major_grid=(1, 1), background_color=(1, 1, 1, 1), major_grid_color=(0, 0, 0, 1), resolution=None, minor_grid_color=(0, 0, 0, 1), minor_grid_n=(5, 5)): super().__init__() self.position = position self.size = size self.cs = cs self._t = time() self.major_grid = major_grid self.major_grid_color = major_grid_color self.minor_grid_color = minor_grid_color self.minor_grid_n = minor_grid_n self.background_color = background_color self._req_uniforms = True self.resolution = resolution or self.size self._dev = False self._mat_model = np.array([ self.resolution.x, 0, 0, 0, 0, self.resolution.y, 0, 0, 0, 0, 1, 0, self.position.x, self.position.y, 0, 1 ], dtype=np.float32).reshape((4, 4)).T self._init_plane() @size.on_change @cs.on_change @major_grid.on_change @minor_grid_n.on_change @minor_grid_width.on_change @major_grid_width.on_change @resolution.on_change @position.on_change def req_uniforms(self, *e): self._req_uniforms = True def upload_uniforms(self): # update model matrix self._mat_model[0][0] = self.resolution[0] self._mat_model[1][1] = self.resolution[1] self._mat_model[3] = self.position self.program.uniform('mat_model', self._mat_model) # -- polar # self.program.uniform('u_limits1',[-5.1,+5.1,-5.1,+5.1]) # self.program.uniform('u_limits2', [-5.0,+5.0, np.pi/6.0, 11.0*np.pi/6.0]) # self.program.uniform('u_major_grid_step', [1.00,np.pi/ 6.0]) # self.program.uniform('u_minor_grid_step', [0.25,np.pi/60.0]) # -- cartesian # add this extra bit and assure that u_limits1 != u_limit2 l1 = self.cs.values * 1.000 mg = self.major_grid.values cs = self.cs.values Mw = self.minor_grid_width * max(1.0, self.resolution[0] / self.size[0]) mw = self.major_grid_width * max(1.0, self.resolution[0] / self.size[0]) aa = self.antialiasing self.program.uniform('u_limits1', l1) self.program.uniform('u_limits2', cs) self.program.uniform('u_major_grid_step', mg) self.program.uniform('u_minor_grid_step', self.major_grid.values / self.minor_grid_n) self.program.uniform('u_major_grid_width', Mw) self.program.uniform('u_minor_grid_width', mw) self.program.uniform('u_major_grid_color', self.major_grid_color) self.program.uniform('u_minor_grid_color', self.minor_grid_color) self.program.uniform('u_resolution', self.resolution) self.program.uniform('u_antialias', aa) self.program.uniform('c_bg', self.background_color) self._req_uniforms = False # print('RES', self.resolution, 'size', self.size, 'CS', self.cs) # print('GRID', self.major_grid) def _init_plane(self): self.program = GridProgram() self.program.uniform_block_binding( 'camera', GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self.upload_uniforms() self.mesh = StridedVertexMesh( mesh3d_rectangle(), GL_TRIANGLES, attribute_locations=self.program.attributes) def dev(self): self.program = GridProgramDev() self.program.uniform_block_binding( 'camera', GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self.upload_uniforms() self._dev = True return self def tick(self): if True or self._req_uniforms: self.upload_uniforms() super().tick() def _render(self): glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) if self._dev: self.program.uniform('dev_color1', (1, 0, 0, 1)) self.program.uniform('dev_color2', (1, 0, 1, 1)) self.program.use() self.mesh.draw() self.program.unuse() if self._dev: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) self.program.uniform('dev_color1', (0, 1, 0, 1)) self.program.uniform('dev_color2', (0, 1, 1, 1)) self.program.use() self.mesh.draw() self.program.unuse() glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
class Plotter2d(Widget): UBO_DTYPE = np.dtype([('mat_cs', np.float32, (4, 4)), ('cs', np.float32, 4), ('cs_size', np.float32, 2)]) # widget configuration size = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4, (0, 0, 0, 1)) # configuration space determines [minx, maxx, miny, maxy] cs = attributes.VectorAttribute(4, (0, 1, 0, 1)) # axes configuration # size of one unit (sx, sy) in configuration space axes_unit = attributes.VectorAttribute(2, (0.25, 0.25)) # division of one axes_unit into sub units minor_axes_n = attributes.VectorAttribute(2, (5, 5)) # the plot plane is implemented via framebuffer. this factor # allows to adjust the resolution of the framebuffer viewport. plot_resolution_factor = attributes.CastedAttribute(float, 1) background_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) plot_background_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) plot_padding = attributes.VectorAttribute(4, (0, 0, 0, 0)) # common precomputed properties cs_size = attributes.ComputedAttribute( cs, descriptor=attributes.VectorAttribute(2), transformation=cs_size) def __init__(self, size, position=(0, 0, 0, 1), cs=(0, 1, 0, 1), style=DEFAULT_STYLE, axes_unit=None): super().__init__() # state self.cs = cs self.size = size self.position = position if axes_unit is not None: self.axes_unit = axes_unit #self.axes_unit = (0.1, 0.1) self._style = Style( { 'border': parse_4f1_1c4, 'background-color': parse_1c4, 'grid-color': parse_1c4, 'grid-sub-color': parse_1c4, 'plot-background-color': parse_1c4, 'plot-padding': parse_4f1, 'min-size': parse_2f1, 'plot-scaling': float, }, style) self.background_color = self._style['background-color'] self.plot_background_color = self._style['plot-background-color'] self.plot_padding = self._style['plot-padding'] self.ubo = None self.on_plot = Event() self._plot_margin = vec4((0, 0, 0, 0)) self.grid = None self.layer = None self._graphs = [] self._graphs_initialized = False self._init() self.a = False self.last_fr = False self.on_plot.once(self.init_graphs) self.cmc = [ [1, 0, 0, 1], [1, 1, 0, 1], [1, 0, 1, 1], [0, 1, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1], ] # -- graph api def init_graphs(self): for graph in self._graphs: graph.init() self._graphs_initialized = True def append(self, graph): self._graphs.append(graph) if self._graphs_initialized: graph.init() graph.resolution = self.plotframe.resulution graph.viewport = self.plotframe.resulution def __iadd__(self, graph): self.append(graph) return self # -- init def _init(self): self._initlayer() self._initplotcam() self._init_grid() self._init_ubo() # -- plot ubo def _init_ubo(self): """ initializes plotting ubo """ self.ubo = BufferObject.to_device(np.zeros(1, dtype=Plotter2d.UBO_DTYPE), target=GL_UNIFORM_BUFFER) buffer_base = GPUPY_GL.CONTEXT.buffer_base('gpupy.plot.plotter2d') self.ubo.bind_buffer_base(buffer_base) self.update_ubo() @cs.on_change def update_ubo(self, *e): self.ubo.host['mat_cs'] = mat_cs(self.cs, self.layer.content_size) self.ubo.host['cs'] = self.cs.values self.ubo.host['cs_size'] = cs_size(self.cs) self.ubo.sync_gpu() # -- grid def _init_grid(self): """ initializes grid component """ major_grid = observables.transform_observables( transformation=grid, observables=(self.axes_unit, self.cs)) self.grid = CartesianGrid( size=self.layer.content_size, position=self.layer.content_position, cs=self.cs, major_grid=major_grid, major_grid_color=self._style['grid-color'], minor_grid_color=self._style['grid-sub-color'], background_color=self.plot_background_color, resolution=self.layer.content_size, minor_grid_n=self.minor_axes_n) #.dev() # -- camera def _initplotcam(self): """ creates a plot camera which is connected to the configuration space """ pos = observables.transform_observables( lambda s: (s[0] * 0.5, s[1] * 0.5, 1), vecn((0, 0, 0)), (self.layer.content_size, )) self.plotcam = Camera2D(self.layer.content_size, pos) # -- container def _initlayer(self): """ initializes main plotcontainer. the plotcontainer manages border, margin padding and contains the main plot framebuffer """ layer = Container(size=self.size, position=self.position, margin=self._plot_margin, padding=self.plot_padding, border=self._style['border'][0], border_color=self._style['border'][1]) self.plotframe = FrameWidget(position=layer.content_position, size=layer.content_size, resulution=layer.content_size, clear_color=(0, 0, 0, 0)) self.layer = layer self.layer.content_size.on_change.append(self.update_ubo) def tick(self): self.on_tick() # -- tick the components self.plotcam.enable() self.layer.tick() self.grid.tick() self.plotframe.tick() # -- graph rendering self.plotframe.use() self.plotcam.enable() self.on_plot() self.ubo.bind_buffer_base( GPUPY_GL.CONTEXT.buffer_base('gpupy.plot.plotter2d')) for graph in self._graphs: graph.tick() graph.render() self.plotframe.unuse() def draw(self): self.grid.render() self.layer.render() self.plotframe.render()
class GLFW_Window(Context): size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(2) title = attributes.CastedAttribute(str, 'window') visible = attributes.CastedAttribute(bool) def __init__(self, size=(400, 400), title='gpupy glfw window', bootstrap=True, widget=None): super().__init__() self._glfw_initialized = False self.size = size self.title = title self.visible = True self._active = False self.active_keys = set() self._in_cycle = False if bootstrap: self.bootstrap() self.make_context() self.widget = widget or (lambda *a: True) @visible.on_change def set_visible(self, visible): if visible: glfwShowWindow(self._handle) else: glfwHideWindow(self._handle) @size.on_change def set_size(self, size): glfwSetWindowSize(self._handle, int(size[0]), int(size[1])) def bootstrap(self): """ start GLFW window context """ if self._glfw_initialized: raise RuntimeError('allready initialized.') self._handle = glfwCreateWindow(int(self.size[0]), int(self.size[1]), self.title) if not self._handle: raise RuntimeError('glfw.CreateWindow() error') glfwWindowHint(GLFW_VISIBLE, int(self.visible)) def _resize_callback(window, width, height): self.size = (width, height) if len(self.on_resize): # at this point we only make a new context if we are not just # within GLFW_Application.cycle method. E.g. if GLFW_Window.set_size() # was performed within the GLFW_Window.cycle() method. if not self._in_cycle: self.make_context() self.on_cycle(self) self.on_resize(self) if not self._in_cycle: glfwSwapBuffers(self._handle) def _key_callback(window, keycode, scancode, action, option): """ put glfw keyboard event data into active and pressed keyboard buffer """ if action == GLFW_PRESS: self.active_keys.add(GLFW_Context.KEYBOARD_MAP[keycode]) elif action == GLFW_RELEASE: self.active_keys.remove(GLFW_Context.KEYBOARD_MAP[keycode]) def _v2_callback(attr, window, width, height): setattr(self, attr, (width, height)) def _close_callback(*e): self.on_close(self) glfwSetWindowSizeCallback(self._handle, _resize_callback) glfwSetFramebufferSizeCallback(self._handle, partial(_v2_callback, 'resolution')) glfwSetWindowCloseCallback(self._handle, _close_callback) glfwSetKeyCallback(self._handle, _key_callback) glfwSetWindowTitle(self._handle, self.title) self.resolution = glfwGetFramebufferSize(self._handle) self._glfw_initialized = True def make_context(self): """ make the glfw handle the current context and assigns itself to GPUPY_GL.CONTEXT. """ if not self._active: glfwMakeContextCurrent(self._handle) self._active = True GPUPY_GL.CONTEXT = self def __call__(self): """ runs the gl cycle and executed the widget. Returns: - Bool: whether the window should be closed or not """ self.make_context() self._in_cycle = True # GLFW close state if glfwWindowShouldClose(self._handle): self._active = False return False # run widget and close if return value is False self.on_cycle(self) success = self.widget() glfwSwapBuffers(self._handle) self._in_cycle = False if not success: self._active = False return False self._active = True return True
class GLFW_Context(Context): KEYBOARD_MAP = { GLFW_KEY_SPACE: KEY_SPACE, GLFW_KEY_APOSTROPHE: KEY_APOSTROPHE, GLFW_KEY_COMMA: KEY_COMMA, GLFW_KEY_MINUS: KEY_MINUS, GLFW_KEY_PERIOD: KEY_PERIOD, GLFW_KEY_SLASH: KEY_SLASH, GLFW_KEY_0: KEY_0, GLFW_KEY_1: KEY_1, GLFW_KEY_2: KEY_2, GLFW_KEY_3: KEY_3, GLFW_KEY_4: KEY_4, GLFW_KEY_5: KEY_5, GLFW_KEY_6: KEY_6, GLFW_KEY_7: KEY_7, GLFW_KEY_8: KEY_8, GLFW_KEY_9: KEY_9, GLFW_KEY_SEMICOLON: KEY_SEMICOLON, GLFW_KEY_EQUAL: KEY_EQUAL, GLFW_KEY_A: KEY_A, GLFW_KEY_B: KEY_B, GLFW_KEY_C: KEY_C, GLFW_KEY_D: KEY_D, GLFW_KEY_E: KEY_E, GLFW_KEY_F: KEY_F, GLFW_KEY_G: KEY_G, GLFW_KEY_H: KEY_H, GLFW_KEY_I: KEY_I, GLFW_KEY_J: KEY_J, GLFW_KEY_K: KEY_K, GLFW_KEY_L: KEY_L, GLFW_KEY_M: KEY_M, GLFW_KEY_N: KEY_N, GLFW_KEY_O: KEY_O, GLFW_KEY_P: KEY_P, GLFW_KEY_Q: KEY_Q, GLFW_KEY_R: KEY_R, GLFW_KEY_S: KEY_S, GLFW_KEY_T: KEY_T, GLFW_KEY_U: KEY_U, GLFW_KEY_V: KEY_V, GLFW_KEY_W: KEY_W, GLFW_KEY_X: KEY_X, GLFW_KEY_Y: KEY_Y, GLFW_KEY_Z: KEY_Z, GLFW_KEY_LEFT_BRACKET: KEY_LEFT_BRACKET, GLFW_KEY_BACKSLASH: KEY_BACKSLASH, GLFW_KEY_RIGHT_BRACKET: KEY_RIGHT_BRACKET, GLFW_KEY_GRAVE_ACCENT: KEY_GRAVE_ACCENT, GLFW_KEY_WORLD_1: KEY_WORLD_1, GLFW_KEY_WORLD_2: KEY_WORLD_2, GLFW_KEY_ESCAPE: KEY_ESCAPE, GLFW_KEY_ENTER: KEY_ENTER, GLFW_KEY_TAB: KEY_TAB, GLFW_KEY_BACKSPACE: KEY_BACKSPACE, GLFW_KEY_INSERT: KEY_INSERT, GLFW_KEY_DELETE: KEY_DELETE, GLFW_KEY_RIGHT: KEY_RIGHT, GLFW_KEY_LEFT: KEY_LEFT, GLFW_KEY_DOWN: KEY_DOWN, GLFW_KEY_UP: KEY_UP, GLFW_KEY_PAGE_UP: KEY_PAGE_UP, GLFW_KEY_PAGE_DOWN: KEY_PAGE_DOWN, GLFW_KEY_HOME: KEY_HOME, GLFW_KEY_END: KEY_END, GLFW_KEY_CAPS_LOCK: KEY_CAPS_LOCK, GLFW_KEY_SCROLL_LOCK: KEY_SCROLL_LOCK, GLFW_KEY_NUM_LOCK: KEY_NUM_LOCK, GLFW_KEY_PRINT_SCREEN: KEY_PRINT_SCREEN, GLFW_KEY_PAUSE: KEY_PAUSE, GLFW_KEY_F1: KEY_F1, GLFW_KEY_F2: KEY_F2, GLFW_KEY_F3: KEY_F3, GLFW_KEY_F4: KEY_F4, GLFW_KEY_F5: KEY_F5, GLFW_KEY_F6: KEY_F6, GLFW_KEY_F7: KEY_F7, GLFW_KEY_F8: KEY_F8, GLFW_KEY_F9: KEY_F9, GLFW_KEY_F10: KEY_F10, GLFW_KEY_F11: KEY_F11, GLFW_KEY_F12: KEY_F12, GLFW_KEY_F13: KEY_F13, GLFW_KEY_F14: KEY_F14, GLFW_KEY_F15: KEY_F15, GLFW_KEY_F16: KEY_F16, GLFW_KEY_F17: KEY_F17, GLFW_KEY_F18: KEY_F18, GLFW_KEY_F19: KEY_F19, GLFW_KEY_F20: KEY_F20, GLFW_KEY_F21: KEY_F21, GLFW_KEY_F22: KEY_F22, GLFW_KEY_F23: KEY_F23, GLFW_KEY_F24: KEY_F24, GLFW_KEY_F25: KEY_F25, GLFW_KEY_KP_0: KEY_KP_0, GLFW_KEY_KP_1: KEY_KP_1, GLFW_KEY_KP_2: KEY_KP_2, GLFW_KEY_KP_3: KEY_KP_3, GLFW_KEY_KP_4: KEY_KP_4, GLFW_KEY_KP_5: KEY_KP_5, GLFW_KEY_KP_6: KEY_KP_6, GLFW_KEY_KP_7: KEY_KP_7, GLFW_KEY_KP_8: KEY_KP_8, GLFW_KEY_KP_9: KEY_KP_9, GLFW_KEY_KP_DECIMAL: KEY_KP_DECIMAL, GLFW_KEY_KP_DIVIDE: KEY_KP_DIVIDE, GLFW_KEY_KP_MULTIPLY: KEY_KP_MULTIPLY, GLFW_KEY_KP_SUBTRACT: KEY_KP_SUBTRACT, GLFW_KEY_KP_ADD: KEY_KP_ADD, GLFW_KEY_KP_ENTER: KEY_KP_ENTER, GLFW_KEY_KP_EQUAL: KEY_KP_EQUAL, GLFW_KEY_LEFT_SHIFT: KEY_LEFT_SHIFT, GLFW_KEY_LEFT_CONTROL: KEY_LEFT_CONTROL, GLFW_KEY_LEFT_ALT: KEY_LEFT_ALT, GLFW_KEY_LEFT_SUPER: KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SHIFT: KEY_RIGHT_SHIFT, GLFW_KEY_RIGHT_CONTROL: KEY_RIGHT_CONTROL, GLFW_KEY_RIGHT_ALT: KEY_RIGHT_ALT, GLFW_KEY_RIGHT_SUPER: KEY_RIGHT_SUPER, GLFW_KEY_MENU: KEY_MENU, GLFW_KEY_LAST: KEY_LAST, } size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(2) title = attributes.CastedAttribute(str, 'window') visible = attributes.CastedAttribute(bool) """ a glfw context manages the window created by glfwCreateWindow. """ def __init__(self, size, title='no title'): super().__init__() self.size = size self.title = title self.visible = True self._handle = None self._glfw_initialized = False self._active = False def _close_callback(self, *e): self.on_close(self) @visible.on_change def set_visible(self, visible): if visible: glfwShowWindow(self._handle) else: glfwHideWindow(self._handle) @size.on_change def set_size(self, size): glfwSetWindowSize(self._handle, int(size[0]), int(size[1])) def bootstrap(self): if self._glfw_initialized: raise RuntimeError('allready initialized.') self._handle = glfwCreateWindow(int(self.size[0]), int(self.size[1]), self.title) if not self._handle: raise RuntimeError('glfw.CreateWindow() error') glfwWindowHint(GLFW_VISIBLE, int(self.visible)) def _v2_callback(attr, window, width, height): setattr(self, attr, (width, height)) glfwSetWindowSizeCallback(self._handle, self.resize_callback) glfwSetFramebufferSizeCallback(self._handle, partial(_v2_callback, 'resolution')) glfwSetWindowCloseCallback(self._handle, self._close_callback) glfwSetKeyCallback(self._handle, self.key_callback) glfwSetWindowTitle(self._handle, 'wewf') self.resolution = glfwGetFramebufferSize(self._handle) self._glfw_initialized = True def context(self): if glfwWindowShouldClose(self._handle): self.on_close(self) if not self._active: glfwMakeContextCurrent(self._handle) self._active = True super().__gl_context_enable__() def resize_callback(self, window, width, height): """ triggers on_resize event queue and swaps GLFW buffers. """ self.size = (width, height) if len(self.on_resize): # at this point we only make a new context if we are not just # within GLFW_Application.cycle method. E.g. if GLFW_Window.set_size() # was performed within the GLFW_Window.cycle() method. if not self._in_cycle: self.__gl_context_enable__() self.on_resize(self) if not self._in_cycle: glfwSwapBuffers(self._handle) def key_callback(self, window, keycode, scancode, action, option): """ put glfw keyboard event data into active and pressed keyboard buffer """ if action == GLFW_PRESS: self.active_keys.add(GLFW_Context.KEYBOARD_MAP[keycode]) elif action == GLFW_RELEASE: self.active_keys.remove(GLFW_Context.KEYBOARD_MAP[keycode]) def cycle(self): if glfwWindowShouldClose(self._handle): raise CloseContextException() self._in_cycle = True self.__gl_context_enable__() self.on_cycle(self) glfwSwapBuffers(self._handle) self._in_cycle = False def __del__(self): glfwDestroyWindow(self._handle)