Created
December 11, 2016 20:58
-
-
Save AnishN/aa3bb27fc9d69319955ed9a8973cd40f to your computer and use it in GitHub Desktop.
PyOpenGL + CEF Python Demo: Play with the controls to change some properties about the spinning triangle in the background! The triangle rendering uses PyOpenGL, while the UI rendering and input use CEF Python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from cefpython3 import cefpython as cef | |
import sys | |
import pygame | |
from PIL import Image | |
from OpenGL.GL import * | |
from OpenGL.GLU import * | |
import inspect | |
import math | |
class CEFSettings: | |
app_settings = { | |
"auto_zooming": "system_dpi", | |
"debug": False, | |
"locales_dir_path": cef.GetModuleDirectory()+"/locales", | |
"resources_dir_path": cef.GetModuleDirectory(), | |
"browser_subprocess_path": "%s/%s" % (cef.GetModuleDirectory(), "subprocess"), | |
"unique_request_context_per_browser": True, | |
"downloads_enabled": False, | |
"remote_debugging_port": 0, | |
"context_menu": { | |
"enabled": False, | |
}, | |
"ignore_certificate_errors": True, | |
} | |
browser_settings = { | |
"file_access_from_file_urls_allowed": True, | |
"universal_access_from_file_urls_allowed": True, | |
} | |
command_line_settings = { | |
"disable-gpu": "", | |
"disable-gpu-compositing": "", | |
"enable-begin-frame-scheduling": "", | |
"disable-d3d11": "", | |
"off-screen-rendering-enabled": "", | |
"off-screen-frame-rate": "60", | |
"disable-gpu-vsync": "", | |
"disable-web-security": "", | |
} | |
class Window: | |
def __init__(self, title, width, height): | |
self.title = title | |
self.width = width | |
self.height = height | |
cef.DpiAware.SetProcessDpiAware() | |
cef.Initialize(CEFSettings.app_settings, CEFSettings.command_line_settings)#need to call before setting up pygame | |
self.setup_pygame() | |
self.setup_ui_texture() | |
self.setup_cef() | |
def setup_pygame(self): | |
size = (self.width, self.height) | |
flags = 0 | |
flags = pygame.OPENGL | pygame.DOUBLEBUF | |
pygame.init() | |
dummy_screen = pygame.display.set_mode((1, 1), flags) | |
max_msaa = glGetIntegerv(GL_MAX_SAMPLES)#unfortunately works only AFTER creating a dummy window | |
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLEBUFFERS, 1) | |
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLESAMPLES, max_msaa) | |
self.screen = pygame.display.set_mode(size, flags) | |
self.window = pygame.display.get_wm_info()["window"] | |
self.clock = pygame.time.Clock() | |
def setup_cef(self): | |
windowInfo = cef.WindowInfo() | |
windowInfo.SetAsOffscreen(self.window) | |
windowInfo.SetTransparentPainting(True) | |
self.browser = cef.CreateBrowserSync(windowInfo, browserSettings=CEFSettings.browser_settings, navigateUrl="file:///transparent-test.html") | |
empty_texture = pygame.Surface((self.width, self.height)) | |
self.client = ClientHandler(self.browser, empty_texture) | |
self.browser.SetClientHandler(self.client) | |
self.js_bindings = cef.JavascriptBindings() | |
def setup_ui_texture(self): | |
self.cef_tex_id = glGenTextures(1) | |
glBindTexture(GL_TEXTURE_2D, self.cef_tex_id) | |
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) | |
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) | |
glBindTexture(GL_TEXTURE_2D, 0) | |
def load_html_ui(self, ui_url): | |
self.browser.LoadUrl(ui_url) | |
def render_html_ui(self): | |
glEnable(GL_TEXTURE_2D) | |
glMatrixMode(GL_PROJECTION) | |
glLoadIdentity() | |
glOrtho(0, self.width, self.height, 0, -1, 1) | |
glMatrixMode(GL_MODELVIEW) | |
glLoadIdentity() | |
glBindTexture(GL_TEXTURE_2D, self.cef_tex_id) | |
tex_surface = pygame.image.tostring(self.client.texture, "RGBA") | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 800, 600, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_surface) | |
glBegin(GL_QUADS) | |
glTexCoord(0, 0) | |
glVertex(0, 0, 0) | |
glTexCoord(0, 1) | |
glVertex(0, self.height, 0) | |
glTexCoord(1, 1) | |
glVertex(self.width, self.height, 0) | |
glTexCoord(1, 0) | |
glVertex(self.width, 0, 0) | |
glEnd() | |
glBindTexture(GL_TEXTURE_2D, 0) | |
def update(self, app_func): | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
cef.Shutdown() | |
pygame.quit() | |
sys.exit() | |
elif event.type == pygame.MOUSEBUTTONDOWN: | |
mouse_x, mouse_y = event.pos | |
if event.button == 1:#left mouse button | |
self.browser.SendMouseClickEvent(mouse_x, mouse_y, cef.MOUSEBUTTON_LEFT, False, 1) | |
elif event.type == pygame.MOUSEBUTTONUP: | |
mouse_x, mouse_y = event.pos | |
if event.button == 1: | |
self.browser.SendMouseClickEvent(mouse_x, mouse_y, cef.MOUSEBUTTON_LEFT, True, 1) | |
elif event.type == pygame.MOUSEMOTION: | |
mouse_x, mouse_y = event.pos | |
self.browser.SendMouseMoveEvent(mouse_x, mouse_y, True) | |
cef.MessageLoopWork() | |
glClear(GL_COLOR_BUFFER_BIT) | |
glEnable(GL_BLEND) | |
glEnable(GL_MULTISAMPLE) | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) | |
app_func() | |
self.render_html_ui() | |
pygame.display.set_caption("CEF PyOpenGL Demo") | |
pygame.display.flip() | |
self.clock.tick(60) | |
class ClientHandler: | |
def __init__(self, browser, texture): | |
self.browser = browser | |
self.texture = texture | |
def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height): | |
data = buffer.GetString(mode="rgba", origin="bottom-left") | |
img = Image.frombuffer('RGBA', (width, height), data, 'raw', 'BGRA') | |
img = pygame.image.fromstring(img.tobytes(), (width, height), "RGBA", True) | |
self.texture = img | |
def GetViewRect(self, browser, rect): | |
width, height = self.texture.get_size() | |
rect.extend([0, 0, width, height]) | |
return True | |
def toggle_color(value): | |
global tri_colored | |
tri_colored = value | |
def update_rotation(value): | |
global tri_speed | |
tri_speed = float(value) | |
def update_scale(scale_value): | |
global tri_scale | |
tri_scale = float(scale_value) | |
def render_tri(): | |
global tri_colored | |
global tri_speed | |
global tri_rotation | |
global tri_scale | |
glDisable(GL_TEXTURE_2D) | |
glMatrixMode(GL_PROJECTION) | |
glLoadIdentity() | |
gluPerspective(30, 800/600.0, 1, 1000) | |
#glOrtho(-1, 1, -1, 1, -1, 1) | |
glMatrixMode(GL_MODELVIEW) | |
glLoadIdentity() | |
tri_rotation += tri_speed | |
glRotate(tri_rotation, 0, 0, 1) | |
glTranslate(0, 0, -10) | |
glScale(tri_scale, tri_scale, tri_scale) | |
glBegin(GL_TRIANGLES) | |
if tri_colored: | |
glColor(1, 0, 0) | |
glVertex(-1, -1/math.sqrt(3), 0) | |
glColor(0, 1, 0) | |
glVertex(0, 2/math.sqrt(3), 0) | |
glColor(0, 0, 1) | |
glVertex(1, -1/math.sqrt(3), 0) | |
else: | |
glColor(0.5, 0.5, 0.5) | |
glVertex(-1, -1/math.sqrt(3), 0) | |
glVertex(0, 2/math.sqrt(3), 0) | |
glVertex(1, -1/math.sqrt(3), 0) | |
glEnd() | |
glColor(1, 1, 1) | |
def main_loop(): | |
render_tri() | |
if __name__ == "__main__": | |
window = Window("PyOpenGL + CEFPython Test", 800, 600) | |
window.load_html_ui("file:///transparent-test.html") | |
window.js_bindings.SetFunction("toggle_color", toggle_color) | |
window.js_bindings.SetProperty("sources", {"toggle_color": inspect.getsource(toggle_color)}) | |
window.js_bindings.SetFunction("update_rotation", update_rotation) | |
window.js_bindings.SetProperty("sources", {"update_rotation": inspect.getsource(update_rotation)}) | |
window.js_bindings.SetFunction("update_scale", update_scale) | |
window.js_bindings.SetProperty("sources", {"update_scale": inspect.getsource(update_scale)}) | |
window.browser.SetJavascriptBindings(window.js_bindings) | |
tri_rotation = 0 | |
tri_colored = False | |
tri_speed = 0.0 | |
tri_scale = 1.0 | |
while True: | |
window.update(main_loop) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<style> | |
@font-face | |
{ | |
font-family: "Droid Sans"; | |
src: url("file:///DroidSans.ttf") format("truetype"); | |
} | |
html * | |
{ | |
font-family: 'Droid Sans'; | |
font-size: 12px; | |
color: white; | |
} | |
body | |
{ | |
background-color: transparent; | |
overflow: hidden; | |
margin: 0 !important; | |
padding: 0 !important; | |
} | |
h1 | |
{ | |
color: #1C4EE6; | |
text-shadow: 0px 2px 5px #222222; | |
font-size: 20pt; | |
text-align: center; | |
} | |
p | |
{ | |
color: white; | |
text-align: center; | |
} | |
#title | |
{ | |
width: 60%; | |
margin-left: auto; | |
margin-right: auto; | |
margin-top: 10px; | |
} | |
#controls | |
{ | |
width: 100%; | |
background-color: rgba(50, 50, 50, 0.4);; | |
height: 50px; | |
bottom: 0px; | |
position: fixed; | |
box-shadow: 0px 0px 5px #222; | |
} | |
#controls_container | |
{ | |
display: flex; | |
align-items: center; | |
justify-content: space-between; | |
height: 50px; | |
margin-left: auto; | |
margin-right: auto; | |
width: 90%; | |
color: #777; | |
} | |
input:focus | |
{ | |
outline: none; | |
} | |
input[type="checkbox"] | |
{ | |
-webkit-appearance:none; | |
height:1em; | |
width:1em; | |
background-color: rgba(50, 50, 50, 1); | |
vertical-align: middle; | |
border: 1px solid #777; | |
box-shadow: 0px 2px 2px #222; | |
} | |
input[type="checkbox"]:checked | |
{ | |
background-color:#0080FF; | |
} | |
input[type="checkbox"]:checked | |
{ | |
background-color:#0080FF; | |
} | |
input[type="number"] | |
{ | |
-webkit-appearance:none; | |
width: 4em; | |
height: 1.5em; | |
border: solid 1px #777; | |
background-color: rgba(50, 50, 50, 1); | |
box-shadow: 0px 2px 2px #222; | |
vertical-align: middle; | |
} | |
input[type=number]::-webkit-outer-spin-button, | |
input[type=number]::-webkit-inner-spin-button | |
{ | |
margin: 0; | |
} | |
input[type=range] | |
{ | |
-webkit-appearance: none; | |
width: 8em; | |
box-shadow: 0px 2px 2px #222; | |
border: 1px solid #777; | |
vertical-align: middle; | |
background-image: -webkit-gradient( | |
linear, | |
left top, | |
right top, | |
color-stop(0.2, #1C4EE6), | |
color-stop(0.2, rgba(50, 50, 50, 1)) | |
); | |
} | |
input[type=range]:after | |
{ | |
color: #ff0000; | |
} | |
input[type=range]::-webkit-slider-runnable-track | |
{ | |
width: 8em; | |
height: 1em; | |
cursor: pointer; | |
} | |
input[type=range]::-webkit-slider-thumb | |
{ | |
height: 1em; | |
width: 0.5em; | |
background: #777; | |
cursor: pointer; | |
-webkit-appearance: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="title"> | |
<h1>PyOpenGL + CEF Python Demo</h1> | |
<p>Play with the controls to change some properties about the spinning triangle in the background! The triangle rendering uses PyOpenGL, while the UI rendering and input use CEF Python.</p> | |
</div> | |
<div id="controls"> | |
<div id="controls_container"> | |
<label>Color: <input type="checkbox" defaultValue="False" onclick="window.toggle_color(this.checked);"></label> | |
<label>Rotation Speed: <input type="number" step="0.1" value="0.0" defaultValue="0.0" onclick="window.update_rotation(this.value);"></label> | |
<label>Scale: <input type="range" min="0.1" max="5.0" step="0.001" value="1.0" onchange="update_slider(this);"></label> | |
</div> | |
</div> | |
<script> | |
function update_slider(slider) | |
{ | |
var norm_value = (slider.value - slider.min)/(slider.max - slider.min); | |
slider.style.backgroundImage = "-webkit-gradient(linear, left top, right top, " | |
+ "color-stop(" + norm_value + ", #1C4EE6), " | |
+ "color-stop(" + norm_value + ", rgba(50, 50, 50, 1)))"; | |
window.update_scale(slider.value); | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment