Skip to content

Instantly share code, notes, and snippets.

@AnishN
Created December 11, 2016 20:58
Show Gist options
  • Save AnishN/aa3bb27fc9d69319955ed9a8973cd40f to your computer and use it in GitHub Desktop.
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.
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)
<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