bgui is a Python library to handle GUIs in the Blender Game Engine.
Author: Mitchell Stokes (Moguri); Email: [email protected]
Python GUI library for the Blender Game Engine
License: MIT License
bgui is a Python library to handle GUIs in the Blender Game Engine.
Author: Mitchell Stokes (Moguri); Email: [email protected]
If you want to override the border of a themed frame to size 0 when creating a new frame bgui.Frame(...,border=0)
the frame will use the border theme value.
You can reproduce this following the next steps:
BorderColor=1, 0.0, 0.0, 1.0
self.win = bgui.Frame(self, size=[0.6, 0.8], border=0,
Hi, y'all!
I'm interested to port this to UPBGE 0.3 or pay someone talented enough for doing it. It does not work because it uses legacy opengl and the latest version of blender/upbge only allows modern OGL. Any bright soul willing to help? ๐
Thank you!
Why in the 0.09 version there isn't a scrollbar widget?
Last Blender build (hash f70d966), Linux Mint Olivia
With the simple example the following error is returned:
Unabled to load the image ../../themes/default/audio.png
Caused by an invalid loaded image using ImageFFmpeg. Unfortunately this error is too common with PNG images (and many other alpha channel involved ones).
I suggest you to workaround the problem using the Qt loader to load the image, and then creating a buffer object for BGE. Following my texture.py
:
# This module encapsulates texture loading so we are not dependent on bge.texture
HAVE_BGE_TEXTURE = False
HAVE_PYQT_TEXTURE = False
from .gl_utils import *
try:
from bge import texture
import aud
HAVE_BGE_TEXTURE = True
except ImportError:
print("Warning: bge cannot be imported")
try:
from PyQt4 import QtOpenGL, QtGui
HAVE_PYQT_TEXTURE = True
except ImportError:
print("Warning: PyQt4 cannot be imported")
try:
from PySide import QtOpenGL, QtGui
HAVE_PYQT_TEXTURE = True
except ImportError:
print("Warning: PySide cannot be imported either")
# We are using the Python duck typing to generate a phony blender image that
# store the image buffer and size
class ImageFromBuff:
def __init__(self, buff, width, height):
"""Build the image data
:param buff: Image data
:param width: Image width
:param height: Image height
"""
self._image = buff
self._size = [width, height]
self._valid = True
@property
def image(self):
"""image data"""
return self._image
@property
def size(self):
"""image size"""
return self._size
@property
def valid(self):
"""bool to tell if an image is available"""
return self._valid
class Texture:
def __init__(self, path, interp_mode):
self._tex_id = glGenTextures(1)
self.size = [0, 0]
self._interp_mode = None
self.path = None
# Setup some parameters
self.bind()
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
self.interp_mode = interp_mode
self.reload(path)
def __del__(self):
glDeleteTextures([self._tex_id])
@property
def interp_mode(self):
return self._interp_mode
@interp_mode.setter
def interp_mode(self, value):
if value != self._interp_mode:
self.bind()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, value)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, value)
self._interp_mode = value
def bind(self):
glBindTexture(GL_TEXTURE_2D, self._tex_id)
class ImageTexture(Texture):
_cache = {}
def __init__(self, image, interp_mode, caching):
self._caching = caching
super().__init__(image, interp_mode);
def reload(self, image):
if image == self.path:
return
img = None
if image in ImageTexture._cache:
# Image has already been loaded from disk, recall it from the cache
img = ImageTexture._cache[image]
else:
# Load the image data from disk, try to use BGE first
if HAVE_BGE_TEXTURE:
img = texture.ImageFFmpeg(image)
img.scale = False
if not img.valid or img.image is None:
img = None
elif self._caching:
ImageTexture._cache[image] = img
# Try to use PyQt (or PySide) if BGE has failed
if img is None and HAVE_PYQT_TEXTURE:
qt_img = QtGui.QImage(image)
if qt_img.isNull():
img = None
else:
# Qt returns the image with a lot of weird format, so some
# operations must be applied before
qt_img = qt_img.convertToFormat(QtGui.QImage.Format_ARGB32)
qt_img = qt_img.mirrored()
qt_img = qt_img.rgbSwapped()
# Now we can extract the image data and create a valid
# object for BGE
data = qt_img.constBits()
size = qt_img.size()
data.setsize(qt_img.byteCount())
data = memoryview(data).tobytes()
channels = len(data) / (size.width() * size.height())
buff = Buffer(GL_BYTE,
[len(data)],
data)
img = ImageFromBuff(buff, size.width(), size.height())
if self._caching:
ImageTexture._cache[image] = img
if img is None:
print("Unable to load the image", image)
return
# Show the image with the appropiated backend
data = img.image
if not img.valid or data is None:
print("Unhandled image exception...", image)
return
# Upload the texture data
self.bind()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.size[0], img.size[1],
0, GL_RGBA, GL_UNSIGNED_BYTE, data)
self.image_size = img.size[:]
# Save the image name
self.path = image
img = None
class VideoTexture(Texture):
def __init__(self, video, interp_mode, repeat, play_audio):
self.repeat = repeat
self.play_audio = play_audio
self.video = None
self.audio = None
super().__init__(video, interp_mode)
def __del__(self):
super().__del__()
if self.audio:
self.audio.stop()
self.video = None
def reload(self, video):
if video == self.path:
return
if USING_BGE_TEXTURE:
vid = texture.VideoFFmpeg(video)
vid.repeat = self.repeat
vid.play()
self.video = vid
data = vid.image
if self.play_audio:
self.audio = aud.device().play(aud.Factory(video))
else:
data = None
if data == None:
print("Unable to load the video", video)
return
self.bind()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, vid.size[0], vid.size[1],
0, GL_RGBA, GL_UNSIGNED_BYTE, data)
self.image_size = vid.size[:]
self.path = video
def update(self):
if not self.video:
return
self.video.refresh()
data = self.video.image
if data:
self.bind()
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.video.size[0], self.video.size[1],
0, GL_RGBA, GL_UNSIGNED_BYTE, data)
def play(self, start, end, use_frames=True, fps=None):
if not self.video:
return
start = float(start)
end = float(end)
if use_frames:
if not fps:
fps = self.video.framerate
print("Using fps:", fps)
start /= fps
end /= fps
if start == end:
end += 0.1
self.video.stop()
self.video.range = [start, end]
self.video.play()
As you can see I first try to load the image with BGE, but if it fails I fallback to the Qt stuff, loading the image (and conveniently formatting it), creating a BGE Buffer and finally storing it in a phony BGE texture.
I hope it helps!
Jose Luis Cercos Pita
I'll start with a quick example:
frame = bgui.Frame(self)
frame.size = [0.5, 0.5] # Normalized values in
print(frame.size) # Non-normalized values out
The above shows some unexpected behaviour (its also the same for position). If I'm writing my UI code, and I'm working in normalized coordinates I don't ever want to see or have to deal with pixel values. The fix for this should be simple enough, just do a check on access and convert to normalized values if needed. The problem is a lot of the existing code expects to receive a non-normalized value from those properties.
A second inconsistency has to do with _base_size
and _base_pos
. I may be wrong here, but from what I can see these represent the underlying coordinate system that bgui widgets use internally. However, depending on whether a widget has BGUI_NO_NORMALIZE
set or not, the values stored will be either normalized or non-normalized. I feel that this behaviour is confusing.
I propose a change to the underlying size and position code to remove these inconsistencies.
_base_x
and _base_y
store the absolute pixel values of the bottom-left corner of the widget and variables _base_width
and _base_height
store the pixel values of the size of the widget_x
and _y
store the relative normalized (or non-normalized, depending on options) value of the bottom-left corner of the widget and variables _width
and _height
store the normalized (or non-normalized) width and height of the widgetx
, y
and position
as well as properties width
, height
and size
for accessing and modifying the data. position
returns [x, y]
, likewise for size
I found myself quite frequently only modifying the x position of a widget, or only changing the width of a widget, so having x
and y
properties will definitely be helpful. However I'm not sure on the usefulness of splitting them up into _base_x
and _base_y
and whether or not its just better to use _base_position
.
As it stands the position and size side of things can be a little hard to predict and I think its important to simplify things. Let me know your thoughts, I'll be happy to lend a hand with code over the next few weeks.
created a fork with the changes and example files
Shouldn't more advanced widgets like scrollable frames be made using simpler widget building blocks like frames, labels etc?
I ask cause I see a few people building them from scratch as opposed to using multiple simple widgets to form more complex ones.
Hi,
I cannot seem to get a correct aspect ratio/normalise text concerning my buttons (or anything) and inside the buttons. Two pictures below to show what I mean. In Windowed mode it looks good, but in Fullscreen it looks bad. I have tried putting Normalise_text = True and changing the aspect ratio but it does not seem to work...
Here is a simplified code of my application :
import bgui
import bgui.bge_utils
import bge
#This is the Main Layout. This never dissapears
class MainLayout(bgui.bge_utils.Layout):
"""A layout showcasing various Bgui features"""
def __init__(self, sys, data):
super().__init__(sys, data)
# Add widgets here
# Use a frame to store all of our widgets
self.frame = bgui.Frame(self, border=0)
self.frame.colors = [(0, 0, 0, 0) for i in range(4)]
# A FrameButton widget
self.btn = bgui.FrameButton(self.frame, text='Import', size=[0.1, 0.05], pos=[0, 0.95],
base_color =(0,255,255,1), options=bgui.BGUI_DEFAULT)
self.btn.on_click = self.button_click
self.btn2 = bgui.FrameButton(self.frame, text='Start Simulation', size=[0.2, 0.05], pos=[0.1, 0.95],
options=bgui.BGUI_DEFAULT)
self.btn2.on_click = self.button_click2
self.btn3 = bgui.FrameButton(self.frame, text='Stop Simulation', size=[0.2, 0.05], pos=[0.3, 0.95],
options=bgui.BGUI_DEFAULT)
self.btn3.on_click = self.button_click3
#Simple TextBlock Widget
self.txt = bgui.TextBlock(self.frame, color =(0,255,255,1), text ='Hello I am testing this',pt_size = 30,pos=[0,-0.95])
#These are the definitions
def button_click(self, widget):
self.lbl.text = 'This is the Import Button'
widget.system.toggle_overlay(testoverlay)
def button_click2(self, widget):
self.lbl.text = "This starts the Simulation"
def button_click3(self, widget):
self.lbl.text = "This Stops the Simulation"
def main(cont):
own = cont.owner
mouse = bge.logic.mouse
if 'sys' not in own:
# Create our system and show the mouse
own['sys'] = bgui.bge_utils.System('../../themes/default')
own['sys'].load_layout(MainLayout, None)
mouse.visible = True
normalise_text=True
else:
own['sys'].run()
normalise_text=True
The TextInput highlight and cursor was using absolute pixel sizes and positions instead of normalized, thanks to MikePan getting a quick solution to set the highlight and cursor in TextInput to no_normalize, you will find the fix in my fork of bgui.
ImageTexture arguments are "options & bgui.BGUI_CACHE", which I suspect is intended to be options | bgui.BGUI_CACHE
In addition to this, you cannot load an image at a later time for an Image if it is not initially given one.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.