ok im having problems porting this to mac i just dont know enough if somebody would be willing to walk me through it i will do all the work… i really want to learn i just need a little guidance from somebody who knows OS X and C++
Vardis - I used the test.html document that is distributed with awesomium. Stuck the relevant files into the ‘bin’ directory and have the awesomium.py and ‘media’ folder in my sample programs. I get the correct display except with no flash animation.
Tried creating my own HTML file with my own SWF but again I can display text etc but no SWF. The flash animation loads fine in my IE browser. Anyone else had similar problems? Is there any other way to get flash animations into Panda?
Are you running on windows or some other platform?
Some things you can try:
-Look in PANDA_HOME/python for a file named awesomium.log. As you might guessed it contains the log output from awesomium, so check in there for any errors.
-Download and try running the official demo from here:
http://princeofcode.com/download.php?target=Awesomium_v1.0_Demo.zip.
If this doesn’t work then awesomium probably can’t render on your machine.
Hi everybody,
I noticed this thread only yesterday and I’m amazed by the potential of creating GUIs mostly using web programming skills and adding a browser to a game.
Vardis, I’m not familiar with the procedures to create python bindings for C++ code. Can you elaborate about what’s the problem you are facing concerning the injection of keyboard events?
In the meantime I’ve tried to come up with a way overcome the lack of keyboard inputs. The code, based on Vardis example, is at the end of the message. It works but has various shortcomings:
- it relies on the messenger hack I described in this thread to catch all events
- I couldn’t find a neat way to catch all key-related events but nothing else. I also wonder if it would still work with non-english/non-western keyboards.
- It requires specific javascript. Unless there’s a way to inject javascript code in a loaded html page this limits its use to pages that are compatible with this hack. So no way to fill input fields across the net.
- It uses a trick, changing the input’s background color, to determine if the incoming key should or should not affect it. Not neat.
- I couldn’t quite figure out how to use javascript’s “this” in highlight() and delight()
- It doesn’t handle character repeat, i.e. holding the backspace to delete the whole string.
Here’s the code. It relies on the binaries Vardis provided at the beginning of this thread.
Save this in a file “inputTest.html”:
<html>
<head>
<script type="text/javascript">
function updateInputField()
{
inputField = document.getElementById('anInput')
// this is needed to prevent all key events
// from altering the content of the input field
if(inputField.style.backgroundColor != "yellow")
return
if (Client.depressedKey != "backspace" && Client.depressedKey != "space")
inputField.value += Client.depressedKey
else if (Client.depressedKey == "space")
inputField.value += " "
else
inputField.value = inputField.value.substring(0, inputField.value.length-1)
}
function highlight()
{
inputField = document.getElementById('anInput')
inputField.style.backgroundColor = "yellow"
}
function delight()
{
inputField = document.getElementById('anInput')
inputField.style.backgroundColor = "white"
}
</script>
</head>
<body>
<form name="aForm">
<input id="anInput" style="border:5px dotted red" type="text" value="Write something here!"
onfocus="highlight()" onblur="delight()"/>
</form>
</body>
</html>
and then save this in a file “inputTest.py”:
import array, sys
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
from pandac.PandaModules import Texture
from pandac.PandaModules import CardMaker
from pandac.PandaModules import NodePath
from pandac.PandaModules import Point3,Vec3,Vec4
from pandac.PandaModules import PNMImage
from pandac.PandaModules import TextureStage
from pandac.PandaModules import WindowProperties
from direct.interval.IntervalGlobal import *
from pawesomium import *
WIDTH = 800
HEIGHT = 512
WIN_WIDTH = 800
WIN_HEIGHT = 600
class HtmlView(DirectObject, WebViewListener):
def __init__(self):
WebViewListener.__init__(self)
# turn off normal mouse controls
base.disableMouse()
# black background suits better in our case
base.win.setClearColor(Vec4(0.0, 0.0, 0.0, 1.0))
self.mx, self.my = 0, 0
self.transparency = True
self.webCore = WebCore(LogLevel.LOG_VERBOSE, True, PixelFormat.PF_BGRA)
self.webCore.setBaseDirectory('./media')
self.webCore.setCustomResponsePage(404, "404response.html");
self.webView = self.webCore.createWebView(WIDTH, HEIGHT, self.transparency, False, 70)
self.webView.setListener(self)
self.htmlFile = "inputTest.html"
self.webView.loadFile(self.htmlFile, '')
self.imgBuffer = array.array('B')
for i in xrange(WIDTH*HEIGHT):
self.imgBuffer.append(0)
self.imgBuffer.append(0)
self.imgBuffer.append(0)
self.imgBuffer.append(255)
self.setupTexture()
self.accept("escape", sys.exit, [0])
self.accept("mouse1", self.mouseDown, [MouseButton.LEFT_MOUSE_BTN])
self.accept("mouse3", self.mouseDown, [MouseButton.RIGHT_MOUSE_BTN])
self.accept("mouse1-up", self.mouseUp, [MouseButton.LEFT_MOUSE_BTN])
self.accept("mouse3-up", self.mouseUp, [MouseButton.RIGHT_MOUSE_BTN])
self.accept("f1", self.toggleTransparency)
self.accept("f2", self.reload)
self.accept("f3", self.zoomIn)
self.accept("f4", self.zoomOut)
taskMgr.add(self.update, 'UpdateTask')
def setupTexture(self):
cm = CardMaker('quadMaker')
cm.setColor(1.0, 1.0, 1.0, 1.0)
aspect = base.camLens.getAspectRatio()
htmlWidth = 2.0*aspect * WIDTH / float(WIN_WIDTH)
htmlHeight = 2.0*float(HEIGHT) / float(WIN_HEIGHT)
# the html area will be center aligned and vertically top aligned
cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, 1.0 - htmlHeight, 1.0)
card = cm.generate()
self.quad = NodePath(card)
self.quad.reparentTo(aspect2d)
self.guiTex = Texture("guiTex")
self.guiTex.setupTexture(Texture.TT2dTexture, WIDTH, HEIGHT, 1, Texture.TUnsignedByte, Texture.FRgba)
self.guiTex.setKeepRamImage(True)
self.guiTex.makeRamImage()
self.guiTex.setWrapU(Texture.WMRepeat)
self.guiTex.setWrapV(Texture.WMRepeat)
ts = TextureStage('webTS')
self.quad.setTexture(ts, self.guiTex)
self.quad.setTexScale(ts, 1.0, -1.0)
self.quad.setTransparency(1)
self.quad.setTwoSided(True)
self.quad.setColor(1.0, 1.0, 1.0, 1.0)
def mouseDown(self, button):
self.webView.injectMouseDown(button)
def mouseUp(self, button):
self.webView.injectMouseUp(button)
def reload(self):
self.webView.loadFile(self.htmlFile, '')
def zoomIn(self):
self.webView.zoomIn()
def zoomOut(self):
self.webView.zoomOut()
def toggleTransparency(self):
self.transparency = not self.transparency
self.webView.setTransparent(self.transparency)
def update(self, task):
if base.mouseWatcherNode.hasMouse():
x, y = self._translateRelativeCoordinates(base.mouseWatcherNode.getMouseX(), base.mouseWatcherNode.getMouseY())
self.webView.injectMouseMove(x, y)
if (self.mx - x) != 0 and (self.my - y) != 0:
self.mx, self.my = x, y
if self.webView.isDirty():
self.webView.render(self.imgBuffer.buffer_info()[0], WIDTH*4, 4, 0)
textureBuffer = self.guiTex.modifyRamImage()
textureBuffer.setData(self.imgBuffer.tostring())
self.webCore.update()
return Task.cont
def _translateRelativeCoordinates(self, x, y):
sx = int((x + 1.0) * 0.5 * base.win.getXSize())
sy = int((-1.0*y + 1.0) * 0.5 * base.win.getYSize())
return sx, sy
# --------------------[ WebViewListener implementation ]--------------------------
def onCallback(self, name, args):
pass
def onBeginNavigation(self, url, frameName):
pass
def onBeginLoading(self, url, frameName, statusCode, mimeType):
pass
def onFinishLoading(self):
pass
def onReceiveTitle(self, title, frameName):
pass
def onChangeTooltip(self, tooltip):
pass
def onChangeCursor(self, cursor):
pass
def onChangeKeyboardFocus(self, isFocused):
pass
def onChangeTargetURL(self, url):
pass
def handleKeyEvents(self, event, arguments):
self.webView.setProperty( "depressedKey", JSValue(event))
self.webView.executeJavascript("updateInputField()", "")
htmlView = HtmlView()
def catchAllEvents(event, arguments):
if event.startswith("shift-"):
event = event[6:].upper()
if len(event) == 1 or event == "backspace" or event == "space":
htmlView.handleKeyEvents(event, arguments)
messenger.catchAllMethod = catchAllEvents
run()
run simply typing “python inputTest.py”
Manu
Note that, for the record, you can accept all button and keystroke events, without having to hack Messenger.py. You do this by telling Panda to throw a single event for button and keystroke events, instead of (or in addition to) its default mode of throwing a different event for each button.
base.buttonThrowers[0].node().setButtonDownEvent('button')
base.buttonThrowers[0].node().setButtonRepeatEvent('button')
base.buttonThrowers[0].node().setKeystrokeEvent('keystroke')
self.accept('button', self.buttonHandler)
self.accept('keystroke', self.keystrokeHandler)
You want to listen for ‘button’ events for things like the tab key, the backspace key, and the arrow keys; but you want the ‘keystroke’ events for actual typing. The difference is that the ‘button’ event refers to a particular button on the keyboard, and includes key-repeat; whereas the ‘keystroke’ event refers to the more abstract concept of what the user’s typing. A ‘button’ will send ‘shift-a’ or ‘shift-2’ but a ‘keystroke’ will send ‘A’ and ‘@’. Also, keystrokes respect os-specific multi-key sequences necessary for typing certain international characters.
David
David: that is niiiiice! Thank you for pointing it out. I used it in the code that follows.
I’ve tried to push things a little to see what I could fix and what I could not using only python/panda+javascript. Specifically I’ve tried to implement as much as possible of the functionality of a TEXTAREA element as it is a little more of a general case compared to an INPUT element. The code follows but this is a summary of what I found:
- as David pointed out the messenger hack is no longer needed: yieppiee!!
- JSValue’s constructor doesn’t like unicode strings. That means that when setting a property from python for use by javascript I must first apply str(), which doesn’t bode well for international characters, does it?
- text insertion now works as you would expect except for some strange beginning-of-the-line issues with newline characters
- text selection works as you would expect
- arrow keys work well to move between adjacent characters. Mostly well moving up and down lines.
- extending the text selection via shift-arrow keys works only forward. Try it out and you’ll see what I mean.
- I tried to implement copy, cut & paste. It shouldn’t be too difficult but currently I cannot find a way to pass data back from javascript. The problem likes in JSArguments, the object returned by onCallback(). I don’t know how to get to the data inside it. Maybe the bindings are not complete?
The good news about this is that most of these problems are only due to the impossibility to simply inject keyboard events directly. If the bindings get expanded to include injectTextEvent most of these issues would disappear.
Some issues are probably deep in awesomium/webkit and might not have an immediate solution.
- If I type enough text to require the side scroller bar an unsightly white square that shouldn’t be there appears underneath the bar.
- document.getSelection() is allegedly supported by webkit but doesn’t seem to be supported by the particular build used by awesomium. This makes things much more complicated for cut&paste purposes unless the bindings are expanded to include WebView’s cur(), paste() and copy() methods.
So, here’s the code:
<!-- File: textareaTest.html -->
<html>
<head>
<script type="text/javascript">
function updateTextarea()
{
inputField = document.activeElement
if (inputField.nodeName != "TEXTAREA")
return
initialCursorPosition = inputField.selectionStart
textEnd = inputField.innerHTML.substring(inputField.selectionEnd)
characterToInsert = Client.depressedKey // Client.depressedKey might
// be needed elsewhere too
if(Client.depressedKey != "backspace")
{
textStart = inputField.innerHTML.substring(0, inputField.selectionStart)
newCursorPosition = initialCursorPosition + 1
}
else
{
characterToInsert = ""
if(inputField.selectionEnd - inputField.selectionStart > 0)
{
textStart = inputField.innerHTML.substring(0, inputField.selectionStart)
newCursorPosition = initialCursorPosition
}
else
{
textStart = inputField.innerHTML.substring(0, inputField.selectionStart-1)
newCursorPosition = initialCursorPosition - 1
}
}
inputField.innerHTML = textStart + characterToInsert + textEnd
inputField.selectionStart = inputField.selectionEnd = newCursorPosition
}
function moveCursor()
{
inputField = document.activeElement
if (inputField.nodeName != "TEXTAREA")
return
currentCursorPosition = inputField.selectionStart
newCursorPosition = currentCursorPosition + Client.cursorShift
inputField.selectionEnd = inputField.selectionStart = newCursorPosition
}
function moveCursorOneLine()
{
inputField = document.activeElement
if (inputField.nodeName != "TEXTAREA")
return
currentCursorPosition = inputField.selectionStart
newCursorPosition = currentCursorPosition + (Client.cursorShift * (inputField.cols - 1))
inputField.selectionEnd = inputField.selectionStart = newCursorPosition
}
function moveSelectionEnd()
{
inputField = document.activeElement
if (inputField.nodeName != "TEXTAREA")
return
// Most text editors handle modifications to the selection
// via shift-arrow keys better than this. It's a start.
currentEndPosition = inputField.selectionEnd
newEndPosition = currentEndPosition + Client.selectionEndShift
inputField.selectionEnd = newEndPosition
}
function moveSelectionEndOneLine()
{
inputField = document.activeElement
if (inputField.nodeName != "TEXTAREA")
return
// Most text editors handle modifications to the selection
// via shift-arrow keys better than this. It's a start.
currentEndPosition = inputField.selectionEnd
newEndPosition = currentEndPosition + (Client.selectionEndShift * (inputField.cols - 1))
inputField.selectionEnd = newEndPosition
}
</script>
</head>
<body>
<textarea id="anInputField" style="border:5px dashed blue;" cols="60" rows="10"
onfocus="highlight(this)" onblur="delight(this)">A textarea field with lots and lots of space...</textarea>
</body>
</html>
## file: textareaTest.py
import array, sys, string
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
from pandac.PandaModules import Texture
from pandac.PandaModules import CardMaker
from pandac.PandaModules import NodePath
from pandac.PandaModules import Point3,Vec3,Vec4
from pandac.PandaModules import PNMImage
from pandac.PandaModules import TextureStage
from pandac.PandaModules import WindowProperties
from direct.interval.IntervalGlobal import *
from pawesomium import *
WIDTH = 800
HEIGHT = 600
WIN_WIDTH = 800
WIN_HEIGHT = 600
class HtmlView(DirectObject, WebViewListener):
def __init__(self):
WebViewListener.__init__(self)
# turn off normal mouse controls
base.disableMouse()
# black background suits better in our case
base.win.setClearColor(Vec4(0.0, 0.0, 0.0, 1.0))
self.mx, self.my = 0, 0
self.transparency = True
self.webCore = WebCore(LogLevel.LOG_VERBOSE, True, PixelFormat.PF_BGRA)
self.webCore.setBaseDirectory('.')
#self.webCore.setCustomResponsePage(404, "404response.html");
self.webView = self.webCore.createWebView(WIDTH, HEIGHT, self.transparency, False, 70)
self.webView.setListener(self)
self.htmlFile = "textareaTest.html"
self.webView.loadFile(self.htmlFile, '')
self.imgBuffer = array.array('B')
for i in xrange(WIDTH*HEIGHT):
self.imgBuffer.append(0)
self.imgBuffer.append(0)
self.imgBuffer.append(0)
self.imgBuffer.append(255)
textClipboard = ""
self.setupEventsHandling()
self.setupTexture()
def setupEventsHandling(self):
self.webView.setCallback("setTextClipboard");
self.accept("escape", sys.exit, [0])
self.accept("mouse1", self.mouseDown, [MouseButton.LEFT_MOUSE_BTN])
self.accept("mouse3", self.mouseDown, [MouseButton.RIGHT_MOUSE_BTN])
self.accept("mouse1-up", self.mouseUp, [MouseButton.LEFT_MOUSE_BTN])
self.accept("mouse3-up", self.mouseUp, [MouseButton.RIGHT_MOUSE_BTN])
self.accept("f1", self.toggleTransparency)
self.accept("f2", self.reload)
self.accept("f3", self.zoomIn)
self.accept("f4", self.zoomOut)
base.buttonThrowers[0].node().setButtonDownEvent('buttonDown')
base.buttonThrowers[0].node().setButtonRepeatEvent('keyrepeat')
base.buttonThrowers[0].node().setKeystrokeEvent('keystroke')
self.accept('buttonDown', self.buttonHandler)
self.accept('keyrepeat', self.buttonHandler)
self.accept('keystroke', self.keystrokeHandler)
taskMgr.add(self.update, 'UpdateTask')
def setupTexture(self):
cm = CardMaker('quadMaker')
cm.setColor(1.0, 1.0, 1.0, 1.0)
aspect = base.camLens.getAspectRatio()
htmlWidth = 2.0*aspect * WIDTH / float(WIN_WIDTH)
htmlHeight = 2.0*float(HEIGHT) / float(WIN_HEIGHT)
# the html area will be center aligned and vertically top aligned
cm.setFrame(-htmlWidth/2.0, htmlWidth/2.0, 1.0 - htmlHeight, 1.0)
card = cm.generate()
self.quad = NodePath(card)
self.quad.reparentTo(aspect2d)
self.guiTex = Texture("guiTex")
self.guiTex.setupTexture(Texture.TT2dTexture, WIDTH, HEIGHT, 1, Texture.TUnsignedByte, Texture.FRgba)
self.guiTex.setKeepRamImage(True)
self.guiTex.makeRamImage()
self.guiTex.setWrapU(Texture.WMRepeat)
self.guiTex.setWrapV(Texture.WMRepeat)
ts = TextureStage('webTS')
self.quad.setTexture(ts, self.guiTex)
self.quad.setTexScale(ts, 1.0, -1.0)
self.quad.setTransparency(1)
self.quad.setTwoSided(True)
self.quad.setColor(1.0, 1.0, 1.0, 1.0)
def mouseDown(self, button):
self.webView.injectMouseDown(button)
def mouseUp(self, button):
self.webView.injectMouseUp(button)
def reload(self):
self.webView.loadFile(self.htmlFile, '')
def zoomIn(self):
self.webView.zoomIn()
def zoomOut(self):
self.webView.zoomOut()
def toggleTransparency(self):
self.transparency = not self.transparency
self.webView.setTransparent(self.transparency)
def update(self, task):
if base.mouseWatcherNode.hasMouse():
x, y = self._translateRelativeCoordinates(base.mouseWatcherNode.getMouseX(), base.mouseWatcherNode.getMouseY())
self.webView.injectMouseMove(x, y)
if (self.mx - x) != 0 and (self.my - y) != 0:
self.mx, self.my = x, y
if self.webView.isDirty():
self.webView.render(self.imgBuffer.buffer_info()[0], WIDTH*4, 4, 0)
textureBuffer = self.guiTex.modifyRamImage()
textureBuffer.setData(self.imgBuffer.tostring())
self.webCore.update()
return Task.cont
def _translateRelativeCoordinates(self, x, y):
sx = int((x + 1.0) * 0.5 * base.win.getXSize())
sy = int((-1.0*y + 1.0) * 0.5 * base.win.getYSize())
return sx, sy
# --------------------[ WebViewListener implementation ]--------------------------
def onBeginNavigation(self, url, frameName):
pass
def onBeginLoading(self, url, frameName, statusCode, mimeType):
pass
def onFinishLoading(self):
pass
def onReceiveTitle(self, title, frameName):
pass
def onChangeTooltip(self, tooltip):
pass
def onChangeCursor(self, cursor):
pass
def onChangeKeyboardFocus(self, isFocused):
pass
def onChangeTargetURL(self, url):
pass
def onCallback(self, name, args):
pass
def buttonHandler(self, event):
if event == "backspace":
self.webView.setProperty("depressedKey", JSValue("backspace"))
self.webView.executeJavascript("updateTextarea()", "")
elif event.startswith("arrow_"):
if event == "arrow_right":
self.webView.setProperty("cursorShift", JSValue(1))
self.webView.executeJavascript("moveCursor()", "")
elif event == "arrow_left":
self.webView.setProperty("cursorShift", JSValue(-1))
self.webView.executeJavascript("moveCursor()", "")
elif event == "arrow_up":
self.webView.setProperty("cursorShift", JSValue(-1))
self.webView.executeJavascript("moveCursorOneLine()", "")
elif event == "arrow_down":
self.webView.setProperty("cursorShift", JSValue(1))
self.webView.executeJavascript("moveCursorOneLine()", "")
elif event.startswith("shift-arrow_"):
if event == "shift-arrow_right":
self.webView.setProperty("selectionEndShift", JSValue(1))
self.webView.executeJavascript("moveSelectionEnd()", "")
elif event == "shift-arrow_left":
self.webView.setProperty("selectionEndShift", JSValue(-1))
self.webView.executeJavascript("moveSelectionEnd()", "")
elif event == "shift-arrow_up":
self.webView.setProperty("selectionEndShift", JSValue(-1))
self.webView.executeJavascript("moveSelectionEndOneLine()", "")
elif event == "shift-arrow_down":
self.webView.setProperty("selectionEndShift", JSValue(1))
self.webView.executeJavascript("moveSelectionEndOneLine()", "")
legalCharacters = string.letters + string.digits + string.punctuation + string.whitespace
def keystrokeHandler(self, event):
if event in self.legalCharacters:
self.webView.setProperty( "depressedKey", JSValue(str(event)))
self.webView.executeJavascript("updateTextarea()", "")
htmlView = HtmlView()
run()
I’m not sure but maybe using the stuff from second-life could be useful:
wiki.secondlife.com/wiki/LLQtWebKit which is GPL
wiki.secondlife.com/wiki/LLMozLib2 which is older but MPL
Ok - propably not…