This commit is contained in:
赵豪 2026-01-13 17:06:06 +08:00
commit 8401142a14
1135 changed files with 164230 additions and 0 deletions

4
.flake8 Normal file
View File

@ -0,0 +1,4 @@
[flake8]
max-line-length=100
exclude=rplibs/*,data/*,hosek_wilkie_scattering,bake_gi,_DEV,*_generated.py,resources_rc.py

89
.gitignore vendored Normal file
View File

@ -0,0 +1,89 @@
# --- Ignored extensions ---
# *.pyd
*.pyc
*.ignore
*.old
*.egg
*.psd
*.blend1
*.blend2
*.pdb
*.stackdump
*.exr
*.trace
*.autogen*
*.mip
mitsuba.*.log
*.exr
*.txo.pz
*.log
*.tmp
*.bak
.DS_Store
Thumbs.db
# --- Ignored folders and patterns ---
temp/
models/
data/film_grain/*.png
data/default_cubemap/filtered/
_DEV
toolkit/pathtracing_reference/mitsuba/
toolkit/pathtracing_reference/scene*.png
toolkit/pathtracing_reference/Scene*.blend
toolkit/pathtracing_reference/envmap.png
rpplugins/clouds/resources/slices/*
toolkit/rp_distributor/built/
__pycache__/
.pytest_cache/
.mypy_cache/
.cache/
build/
dist/
*.egg-info/
htmlcov/
# Common IDE / venvs
.idea/
.vscode
.venv
.env
.python-version
# Big Models
assets/big_models/
# Samples folder
samples/
test_models/
# Generated pipeline assets
data/**/*.txo
toolkit/**/__pycache__/
# --- Ignored files ---
TODO.txt
*.flag
desktop.ini
scattering_lut.png
old/
data/gui/loading_screen
data/environment_brdf/scene.png
data/environment_brdf/res/scene.png
toolkit/pathtracing_reference/batch_compare/
toolkit/pathtracing_reference/difference.png
toolkit/pathtracing_reference/res/Scene.blend
toolkit/pathtracing_reference/res/scene.png
toolkit/pathtracing_reference/scene*.png
toolkit/pathtracing_reference/res/tex/envmap.png
_tmp_material.py
*/poisson_disk_generator/source/config_module*
_bake_params*
raw-bake.png
toolkit/bake_gi/resources/*.bam
toolkit/bake_gi/resources/*.blend
toolkit/bake_gi/scene

379
.pylintrc Normal file
View File

@ -0,0 +1,379 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS,rplibs,.git,scripts,build.py
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=1
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality.
optimize-ast=yes
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,E0611,W0622,W0201,C0111,R0201,R0913,R0902,R0904,R0914
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[BASIC]
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,input,xrange,iteritems,raw_input
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,x,y,r,g,b,a,uv,w,h,np
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata,tmp,temp
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[ELIF]
# Maximum number of nested blocks for function / method body
max-nested-blocks=4
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=8
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set). This supports can work
# with qualified names.
ignored-classes=
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setup,init,load,create
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of boolean expressions in a if statement
max-bool-expr=5
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

18
.travis.yml Normal file
View File

@ -0,0 +1,18 @@
language: cpp
sudo: required
dist: trusty
compiler: gcc
addons:
apt:
sources:
- sourceline: "deb http://archive.panda3d.org/ubuntu/ trusty-dev main"
packages:
- cmake
- libeigen3-dev
- libfreetype6-dev
- panda3d1.10
script:
- export PYTHONPATH=${PYTHONPATH}:/usr/lib/python2.7/dist-packages
- export PYTHONPATH=${PYTHONPATH}:/usr/share/panda3d
- python2.7 setup.py --ci-build

339
Builtin/Elements.py Normal file
View File

@ -0,0 +1,339 @@
"""
OUTDATED
Do not use anymore.
"""
import colorsys
from LUIObject import LUIObject
from LUISlider import LUISlider
from LUISprite import LUISprite
from LUIVerticalLayout import LUIVerticalLayout
from LUICallback import LUICallback
from LUILabel import LUILabel
from LUIFrame import LUIFrame
from LUIButton import LUIButton
class LUISliderWithLabel(LUIObject, LUICallback):
def __init__(self, parent=None, width=100.0, filled=False, min_value=0, max_value=1.0, precision=2, value=None):
LUIObject.__init__(self, x=0, y=0, w=width, h=0)
LUICallback.__init__(self)
max_numbers_before = max(len(str(int(max_value))), len(str(int(min_value))))
number_space_required = max_numbers_before
if precision > 0:
number_space_required += 1 + precision
pixels_per_number = 7
self.precision = precision
self.slider = LUISlider(self, width=width - pixels_per_number * number_space_required - 5, filled=filled, min_value=min_value, max_value=max_value, value=value)
self.label = LUILabel(parent=self, shadow=True, text=u"1.23")
self.label.right = 0
self.label.top = self.label.height - self.slider.height
self.label.color = (1,1,1,0.5)
self.slider.add_change_callback(self._on_slider_changed)
self.slider.add_change_callback(self._trigger_callback)
self._on_slider_changed(self.slider, self.slider.get_value())
if parent is not None:
self.parent = parent
self.fit_to_children()
def get_value(self):
return self.slider.get_value()
def set_value(self, val):
self.slider.set_value(val)
def _on_slider_changed(self, obj, value):
self.label.text = ("{:." + str(self.precision) + "f}").format(value)
class LUIKeyMarker(LUIObject):
def __init__(self, parent=None, key=u"A"):
LUIObject.__init__(self)
self.bgLeft = LUISprite(self, "Keymarker_Left", "skin")
self.bgMid = LUISprite(self, "Keymarker", "skin")
self.bgRight = LUISprite(self, "Keymarker_Right", "skin")
self.label = LUILabel(parent=self, text=key, shadow=True)
self.label.centered = (True, True)
self.label.margin = (-3, 0, 0, -1)
self.margin = (-1, 0, 0, -1)
self.set_key(key)
if parent is not None:
self.parent = parent
self.fit_to_children()
def set_key(self, key):
self.label.set_text(key)
self.width = self.label.width + self.bgLeft.width + self.bgRight.width + 7
self.bgMid.width = self.width - self.bgLeft.width - self.bgRight.width
self.bgMid.left = self.bgLeft.width
self.bgRight.left = self.bgMid.width + self.bgMid.left
self.fit_to_children()
class LUIKeyInstruction(LUIObject):
def __init__(self, parent=None, key=u"A", instruction=u"Instruction"):
LUIObject.__init__(self)
self.marker = LUIKeyMarker(parent=self, key=key)
self.instructionLabel = LUILabel(parent=self, text=instruction, shadow=True)
self.instructionLabel.centered = (False, True)
self.instructionLabel.margin.top = -4
self.set_key(key)
def set_key(self, key):
self.marker.set_key(key)
self.instructionLabel.left = self.marker.width + 5
self.fit_to_children()
class LUIColorpicker(LUIObject):
def __init__(self, parent=None, color=None):
LUIObject.__init__(self, x=0, y=0, w=27, h=27)
self.previewBg = LUISprite(self, "ColorpickerPreviewBg", "skin")
self.filler = LUISprite(self, "blank", "skin")
self.filler.width = 21
self.filler.height = 21
self.filler.pos = (5, 5)
self.filler.color = (0.2,0.6,1.0,1.0)
self.overlay = LUISprite(self, "ColorpickerPreviewOverlay", "skin")
self.overlay.pos = (2, 2)
self.overlay.bind("click", self._open_dialog)
self.fit_to_children()
self.popup = LUIColorpickerPopup(self)
self.popup.hide()
if color is not None:
self.colorValue = color
else:
# My favourite color
self.colorValue = (0.2, 0.6, 1.0)
self.set_color_value(self.colorValue)
self.popup.add_change_callback(self._on_popup_color_changed)
if parent is not None:
self.parent = parent
def _open_dialog(self, event):
if self.has_focus():
self.blur()
else:
self.request_focus()
def on_focus(self, event):
self.popup._load_rgb(self.colorValue)
self.popup.open_at(self, 14.0)
def set_color_value(self, rgb):
self.colorValue = rgb
self.filler.color = rgb
def get_color_value(self):
return self.colorValue
def on_tick(self, event):
self.popup._update(event)
def on_blur(self, event):
self.popup.close()
def _on_popup_color_changed(self, popup, rgb):
self.set_color_value(rgb)
def _on_popup_closed(self):
self.blur()
class LUIPopup(LUIFrame):
def __init__(self, parent=None, width=200, height=200):
LUIFrame.__init__(self, parent=parent, width=width, height=height, padding=10, innerPadding=0)
self.topmost = True
self.borderSize = 33
self.content.bind("click", self._on_content_click)
def open_at(self, targetElement, distance):
self.show()
targetPos = targetElement.get_abs_pos()+ targetElement.get_size() / 2
showAbove = targetPos.y > self.height - self.borderSize
showLeft = targetPos.x > self.width - self.borderSize
relative = self.get_relative_pos(targetPos)
self.pos += relative
if showLeft:
self.left -= self.width - self.borderSize
self.left += 25
else:
self.left -= self.borderSize
self.left -= 25
if showAbove:
self.top -= distance
self.top -= self.height - self.borderSize
else:
self.top += distance
self.top -= self.borderSize
def _on_content_click(self, event):
pass
def close(self):
self.hide()
class LUIColorpickerPopup(LUIPopup, LUICallback):
def __init__(self, parent=None):
LUIPopup.__init__(self, parent=parent, width=240, height=146)
LUICallback.__init__(self)
self.field = LUIObject(self.content, x=0, y=0, w=128, h=128)
self.fieldBG = LUISprite(self.field, "blank", "skin")
self.fieldBG.size = (128, 128)
self.fieldBG.color = (0.2,0.6,1.0)
self.fieldFG = LUISprite(self.field, "ColorpickerFieldOverlay", "skin")
self.fieldFG.pos = (-2, 0)
self.fieldBG.bind("mousedown", self._start_field_dragging)
self.fieldBG.bind("mouseup", self._stop_field_dragging)
self.fieldHandle = LUISprite(self.field, "ColorpickerFieldHandle", "skin")
self.fieldHandle.bind("mousedown", self._start_field_dragging)
self.fieldHandle.bind("mouseup", self._stop_field_dragging)
self.fieldDragging = False
self.hueSlider = LUIObject(self.content, x=140, y=0, w=40, h=128)
self.hueSliderFG = LUISprite(self.hueSlider, "ColorpickerHueSlider", "skin")
self.hueHandle = LUISprite(self.hueSlider, "ColorpickerHueHandle", "skin")
self.hueHandle.left = (self.hueSliderFG.width - self.hueHandle.width) / 2.0
self.hueHandle.top = 50
self.hueDragging = False
self.hueSlider.bind("mousedown", self._start_hue_dragging)
self.hueSlider.bind("mouseup", self._stop_hue_dragging)
self.labels = LUIVerticalLayout(self.content, width=40)
self.labels.pos = (177, 42)
colors = [u"R", u"G", u"B"]
self.colorLabels = []
for color in colors:
label = LUILabel(text=color, shadow=True)
label.color = (1,1,1,0.3)
valueLabel = LUILabel(text=u"255", shadow=True)
valueLabel.right = 0
self.labels.add(label, valueLabel)
self.colorLabels.append(valueLabel)
self.activeColor = LUIObject(self.content, x=177, y=0)
self.activeColorBG = LUISprite(self.activeColor, "blank", "skin")
self.activeColorFG = LUISprite(self.activeColor, "ColorpickerActiveColorOverlay", "skin")
self.activeColorBG.size = (40, 40)
self.activeColorBG.pos = (2, 0)
self.activeColorBG.color = (0.2,0.6,1.0,1.0)
self.closeButton = LUIButton(parent=self.content, text=u"Done", width=45, template="ButtonGreen")
self.closeButton.left = 177
self.closeButton.top = 98
self.closeButton.bind("click", self._close_popup)
self._set_hue(0.5)
self._set_sat_val(0.5, 0.5)
self.widget = parent
def _load_rgb(self, rgb):
hsv = colorsys.rgb_to_hsv(*rgb)
self._set_hue(hsv[0])
self._set_sat_val(hsv[1], hsv[2])
def _close_popup(self, event):
self.widget._on_popup_closed()
self.close()
def _update(self, event):
if self.hueDragging:
offset = event.coordinates.y - self.hueSliderFG.abs_pos.y
offset /= 128.0
offset = 1.0 - max(0.0, min(1.0, offset))
self._set_hue(offset)
if self.fieldDragging:
offset = event.coordinates - self.fieldBG.abs_pos
saturation = max(0.0, min(1.0, offset.x / 128.0))
value = 1.0 - max(0.0, min(1.0, offset.y / 128.0))
self._set_sat_val(saturation, value)
self._update_color()
def _set_sat_val(self, sat, val):
self.saturation = sat
self.valueValue = val
self.fieldHandle.top = (1.0 - self.valueValue) * 128.0 - self.fieldHandle.height / 2.0
self.fieldHandle.left = self.saturation * 128.0 - self.fieldHandle.width / 2.0
def _set_hue(self, hue):
self.hueValue = min(0.999, hue)
self.hueHandle.top = (1.0-hue) * 128.0 - self.hueHandle.height / 2
self.fieldBG.color = colorsys.hsv_to_rgb(self.hueValue, 1, 1)
def _update_color(self):
rgb = colorsys.hsv_to_rgb(self.hueValue, self.saturation, self.valueValue)
self.activeColorBG.color = rgb
self.colorLabels[0].set_text(str(int(rgb[0]*255.0)))
self.colorLabels[1].set_text(str(int(rgb[1]*255.0)))
self.colorLabels[2].set_text(str(int(rgb[2]*255.0)))
self._trigger_callback(rgb)
def _start_field_dragging(self, event):
if not self.fieldDragging:
self.fieldDragging = True
def _stop_field_dragging(self, event):
if self.fieldDragging:
self.fieldDragging = False
def _start_hue_dragging(self, event):
if not self.hueDragging:
self.hueDragging = True
def _stop_hue_dragging(self, event):
if self.hueDragging:
self.hueDragging = False

100
Builtin/LUIBlockText.py Normal file
View File

@ -0,0 +1,100 @@
from panda3d.core import LVecBase2i
from LUIObject import LUIObject
from LUILabel import LUILabel
from LUIInitialState import LUIInitialState
__all__ = ["LUIBlockText"]
class LUIBlockText(LUIObject):
""" Small helper class to format labels into paragraphs.
Uses LUILabels internally """
def __init__(self, **kwargs):
""" Creates a new block of text. """
LUIObject.__init__(self)
LUIInitialState.init(self, kwargs)
self._cursor = LVecBase2i(0)
self._last_size = 14
self.labels = []
def clear(self):
""" Removes all text from this label and resets it to the initial state.
This will also detach the sub-labels from this label. """
self._cursor.set(0, 0)
self.labels = []
self.remove_all_children()
def newline(self, font_size=None):
""" Moves the cursor to the next line. The font size controls how much
the cursor will move. By default, the font size of the last added text
is used, or if no text was added yet, a size of 14."""
self._cursor.x = 0
if font_size is None:
font_size = self._last_size
self._cursor.y += font_size + 2
def add(self, *args, **kwargs):
""" Appends a new text. The arguments are equal to the arguments of
LUILabel. The arguments shouldn't contain information about the
placement like top_left, or center_vertical, since the labels are
placed at explicit positions. """
self._last_size = kwargs.get("font_size", 14)
label = LUILabel(parent=self, left=self._cursor.x, top=self._cursor.y, width=self.get_width(),
*args, **kwargs)
self.labels.append(label)
# This is a bit of a hack, we should use a horizontal layout, but we
# don't for performance reasons.
self._cursor.y += label.text_handle.height
# After every paragraph, we add a new line.
self.newline()
def set_text(self, text):
""" Replaces the text with new text """
self.clear()
self.add(text=text)
def update_height(self):
""" Updates the height of the element, adding a newline to the end of
every paragraph """
top = 0
for child in self.labels:
child.top = top
top += child._text.height
# Newline
top += self._last_size + 2
def set_wrap(self, wrap):
""" Sets text wrapping for the element. Wrapping breaks lines on
spaces, and breaks words if the word is longer than the line
length. """
for child in self.children:
for c in child.children:
c.set_wordwrap(wrap)
self.update_height()
def set_width(self, width):
""" Sets the width of this element, and turns on wrapping. """
for child in self.children:
child.set_width(width)
# Need to force an update to the text when the width changes.
for c in child.children:
c.set_wordwrap(True)
self.update_height()

52
Builtin/LUIButton.py Normal file
View File

@ -0,0 +1,52 @@
from LUIObject import LUIObject
from LUILayouts import LUIHorizontalStretchedLayout
from LUILabel import LUILabel
from LUIInitialState import LUIInitialState
__all__ = ["LUIButton"]
class LUIButton(LUIObject):
""" Simple button, containing three sprites and a label. """
def __init__(self, text="Button", template="ButtonDefault", **kwargs):
""" Constructs a new button. The template controls which sprites to use.
If the template is "ButtonDefault" for example, the sprites
"ButtonDefault_Left", "ButtonDefault" and "ButtonDefault_Right" will
be used. The sprites used when the button is pressed should be named
"ButtonDefaultFocus_Left" and so on then.
If an explicit width is set on the button, the button will stick to
that width, otherwise it will automatically resize to fit the label """
LUIObject.__init__(self, x=0, y=0, solid=True)
self._template = template
self._layout = LUIHorizontalStretchedLayout(
parent=self, prefix=self._template, width="100%")
self._label = LUILabel(parent=self, text=text)
self._label.z_offset = 1
self._label.center_vertical = True
self._label.margin = 0, 20, 0, 20
self.margin.left = -1
LUIInitialState.init(self, kwargs)
@property
def text(self):
""" Returns the current label text of the button """
return self._label.text
@text.setter
def text(self, text):
""" Sets the label text of the button """
self._label.text = text
def on_mousedown(self, event):
""" Internal on_mousedown handler. Do not override """
self._layout.prefix = self._template + "Focus"
self._label.margin.top = 1
def on_mouseup(self, event):
""" Internal on_mouseup handler. Do not override """
self._layout.prefix = self._template
self._label.margin.top = 0

83
Builtin/LUICheckbox.py Normal file
View File

@ -0,0 +1,83 @@
from __future__ import division
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUILabel import LUILabel
from LUIInitialState import LUIInitialState
__all__ = ["LUICheckbox"]
class LUICheckbox(LUIObject):
""" This is a simple checkbox, including a Label. The checkbox can either
be checked or unchecked. """
def __init__(self, checked=False, label=u"Checkbox", **kwargs):
""" Constructs a new checkbox with the given label and state. By default,
the checkbox is not checked. """
LUIObject.__init__(self, x=0, y=0, solid=True)
self._checked = checked
self._checkbox_sprite = LUISprite(self, "Checkbox_Default", "skin")
self._label = LUILabel(parent=self, text=label, margin=(0, 0, 0, 25),
center_vertical=True, alpha=0.4)
self._hovered = False
self._update_sprite()
LUIInitialState.init(self, kwargs)
@property
def checked(self):
""" Returns True if the checkbox is currently checked """
return self._checked
@checked.setter
def checked(self, checked):
""" Sets the checkbox state """
self._checked = checked
self._update_sprite()
def toggle(self):
""" Toggles the checkbox state """
self.checked = not self.checked
@property
def label(self):
""" Returns a handle to the label, so it can be modified """
return self._label
@property
def sprite(self):
""" Returns a handle to the internal checkbox sprite """
return self._checkbox_sprite
def on_click(self, event):
""" Internal onclick handler. Do not override """
self._checked = not self._checked
self.trigger_event("changed")
self._update_sprite()
def on_mousedown(self, event):
""" Internal mousedown handler. """
self._checkbox_sprite.color = (0.9, 0.9, 0.9, 1.0)
def on_mouseup(self, event):
""" Internal on_mouseup handler. """
self._checkbox_sprite.color = (1, 1, 1, 1)
def on_mouseover(self, event):
""" Internal mouseover handler """
self._hovered = True
self._update_sprite()
def on_mouseout(self, event):
""" Internal mouseout handler """
self._hovered = False
self._update_sprite()
def _update_sprite(self):
""" Internal method to update the sprites """
img = "Checkbox_Checked" if self._checked else "Checkbox_Default"
if self._hovered:
img += "Hover"
self._checkbox_sprite.set_texture(img, "skin")

View File

@ -0,0 +1,47 @@
from panda3d.core import LVecBase2i
from LUIObject import LUIObject
from LUILabel import LUILabel
from LUIInitialState import LUIInitialState
__all__ = ["LUIFormattedLabel"]
class LUIFormattedLabel(LUIObject):
""" Small helper class to build a text consisting of different formatted
parts of text. Uses LUILabels internally """
def __init__(self, **kwargs):
""" Creates a new formatted label. """
LUIObject.__init__(self)
LUIInitialState.init(self, kwargs)
self._cursor = LVecBase2i(0)
self._last_size = 14
def clear(self):
""" Removes all text from this label and resets it to the initial state.
This will also detach the sub-labels from this label. """
self._cursor.set(0, 0)
self.remove_all_children()
def newline(self, font_size=None):
""" Moves the cursor to the next line. The font size controls how much
the cursor will move. By default, the font size of the last added text
is used, or if no text was added yet, a size of 14."""
self._cursor.x = 0
if font_size is None:
font_size = self._last_size
self._cursor.y += font_size + 2
def add(self, *args, **kwargs):
""" Appends a new text. The arguments are equal to the arguments of
LUILabel. The arguments shouldn't contain information about the
placement like top_left, or center_vertical, since the labels are
placed at explicit positions. """
self._last_size = kwargs.get("font_size", 14)
label = LUILabel(parent=self, left=self._cursor.x, top=self._cursor.y,
*args, **kwargs)
# This is a bit of a hack, we should use a horizontal layout, but we
# don't for performance reasons.
self._cursor.x += label.text_handle.width

66
Builtin/LUIFrame.py Normal file
View File

@ -0,0 +1,66 @@
from __future__ import print_function
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUILayouts import LUICornerLayout
from LUIInitialState import LUIInitialState
from LUIScrollableRegion import LUIScrollableRegion
__all__ = ["LUIFrame"]
class LUIFrame(LUIObject):
""" A container which can store multiple ui-elements. If you don't want a
border/background, you should use an empty LUIObject as container instead.
"""
FS_sunken = 1
FS_raised = 2
def __init__(self, inner_padding=5, scrollable=False, style=FS_raised,
**kwargs):
""" Creates a new frame with the given options and style. If scrollable
is True, the contents of the frame will scroll if they don't fit into
the frame height. inner_padding only has effect if scrollable is True.
You can call fit_to_children() to make the frame fit automatically to
it's contents."""
LUIObject.__init__(self)
# Each *style* has a different border size (size of the shadow). The
# border size shouldn't get calculated to the actual framesize, so we
# are determining it first and then substracting it.
# TODO: We could do this automatically, determined by the sprite size
# probably?
self._border_size = 0
self.padding = 10
self.solid = True
prefix = ""
if style == LUIFrame.FS_raised:
temp = LUISprite(self, "Frame_Left", "skin")
self._border_size = temp.width
self.remove_child(temp)
prefix = "Frame_"
elif style == LUIFrame.FS_sunken:
self._border_size = 0
prefix = "SunkenFrame_"
else:
raise Exception("Unkown LUIFrame style: " + style)
self._scrollable = scrollable
self._layout = LUICornerLayout(parent=self, image_prefix=prefix)
self._layout.margin = -(self.padding.top + self._border_size)
if self._scrollable:
self._content = LUIObject(self)
self._content.size = (self.width, self.height)
self._content.pos = (self._border_size, self._border_size)
self._scroll_content = LUIScrollableRegion(
self._content,
width=self.width - 2 * self.padding.left,
height=self.height - 2 * self.padding.left,
padding=inner_padding)
self.content_node = self._scroll_content.content_node
LUIInitialState.init(self, kwargs)

View File

@ -0,0 +1,20 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUIHorizontalLayout as _LUIHorizontalLayout
from LUIInitialState import LUIInitialState
__all__ = ["LUIHorizontalLayout"]
class LUIHorizontalLayout(_LUIHorizontalLayout):
""" This is a wrapper class for the C++ LUIHorizontalLayout class, to be
able to use it in a more convenient way. It leverages LUIInitialState
to be able to pass arbitrary keyword arguments. """
def __init__(self, parent=None, spacing=0.0, **kwargs):
_LUIHorizontalLayout.__init__(self, parent, spacing)
LUIInitialState.init(self, kwargs)

View File

@ -0,0 +1,41 @@
__all__ = ["LUIInitialState"]
class LUIInitialState:
""" Small helper class to pass keyword arguments to the LUI-objects. It takes
all keyword arguments of a given call, and calls obj.<kwarg> = <value> for
each keyword. It usually is called at the end of the __init__ method. """
def __init__(self):
raise Exception("LUIInitialState is a static class")
# Some properties have alternative names, under which they can be accessed.
__MAPPINGS = {
"x": "left",
"y": "top",
"w": "width",
"h": "height"
}
@classmethod
def init(cls, obj, kwargs):
""" Applies all keyword arguments as properties. For example, passing
dict({"left": 10, "top": 3, "color": (0.2, 0.6, 1.0)}) results in
behaviour similar to:
element.left = 10
element.top = 3
element.color = 0.2, 0.6, 1.0
Calling this method allows setting arbitrary properties in
constructors, without having to specify each possible keyword argument.
"""
for arg_name, arg_val in kwargs.items():
arg_name = cls.__MAPPINGS.get(arg_name, arg_name)
if hasattr(obj, arg_name):
setattr(obj, arg_name, arg_val)
else:
raise AttributeError("{0} has no attribute {1}".format(
obj.__class__.__name__, arg_name))

219
Builtin/LUIInputField.py Normal file
View File

@ -0,0 +1,219 @@
import re
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUILabel import LUILabel
from LUIInitialState import LUIInitialState
from LUILayouts import LUIHorizontalStretchedLayout
__all__ = ["LUIInputField"]
class LUIInputField(LUIObject):
""" Simple input field, accepting text input. This input field supports
entering text and navigating. Selecting text is (currently) not supported.
The input field also supports various keyboard shortcuts:
[pos1] Move to the beginning of the text
[end] Move to the end of the text
[arrow_left] Move one character to the left
[arrow_right] Move one character to the right
[ctrl] + [arrow_left] Move to the left, skipping over words
[ctrl] + [arrow_right] Move to the right, skipping over words
[escape] Un-focus input element
"""
re_skip = re.compile("\W*\w+\W")
def __init__(self, parent=None, width=200, placeholder=u"Enter some text ..", value=u"", **kwargs):
""" Constructs a new input field. An input field always needs a width specified """
LUIObject.__init__(self, x=0, y=0, solid=True)
self.set_width(width)
self._layout = LUIHorizontalStretchedLayout(parent=self, prefix="InputField", width="100%")
# Container for the text
self._text_content = LUIObject(self)
self._text_content.margin = (5, 7, 5, 7)
self._text_content.clip_bounds = (0,0,0,0)
self._text_content.set_size("100%", "100%")
# Scroller for the text, so we can move right and left
self._text_scroller = LUIObject(parent=self._text_content)
self._text_scroller.center_vertical = True
self._text = LUILabel(parent=self._text_scroller, text="")
# Cursor for the current position
self._cursor = LUISprite(self._text_scroller, "blank", "skin", x=0, y=0, w=2, h=15)
self._cursor.color = (0.5, 0.5, 0.5)
self._cursor.margin.top = 2
self._cursor.z_offset = 20
self._cursor_index = 0
self._cursor.hide()
self._value = value
# Placeholder text, shown when out of focus and no value exists
self._placeholder = LUILabel(parent=self._text_content, text=placeholder, shadow=False,
center_vertical=True, alpha=0.2)
# Various states
self._tickrate = 1.0
self._tickstart = 0.0
self._render_text()
if parent is not None:
self.parent = parent
LUIInitialState.init(self, kwargs)
@property
def value(self):
""" Returns the value of the input field """
return self._value
@value.setter
def value(self, new_value):
""" Sets the value of the input field """
self._value = new_value
self._render_text()
self.trigger_event("changed", self._value)
def clear(self):
""" Clears the input value """
self.value = u""
@property
def cursor_pos(self):
""" Set the cursor position """
return self._cursor_index
@cursor_pos.setter
def cursor_pos(self, pos):
""" Set the cursor position """
if pos >= 0:
self._cursor_index = max(0, min(len(self._value), pos))
else:
self._cursor_index = max(len(self._value) + pos + 1, 0)
self._reset_cursor_tick()
self._render_text()
def on_tick(self, event):
""" Tick handler, gets executed every frame """
frame_time = globalClock.get_frame_time() - self._tickstart
show_cursor = frame_time % self._tickrate < 0.5 * self._tickrate
if show_cursor:
self._cursor.color = (0.5, 0.5, 0.5, 1)
else:
self._cursor.color = (1, 1, 1, 0)
def on_click(self, event):
""" Internal on click handler """
self.request_focus()
def on_mousedown(self, event):
""" Internal mousedown handler """
local_x_offset = self._text.text_handle.get_relative_pos(event.coordinates).x
self.cursor_pos = self._text.text_handle.get_char_index(local_x_offset)
def _reset_cursor_tick(self):
""" Internal method to reset the cursor tick """
self._tickstart = globalClock.get_frame_time()
def on_focus(self, event):
""" Internal focus handler """
self._cursor.show()
self._placeholder.hide()
self._reset_cursor_tick()
self._layout.color = (0.9, 0.9, 0.9, 1)
def on_keydown(self, event):
""" Internal keydown handler. Processes the special keys, and if none are
present, redirects the event """
key_name = event.message
if key_name == "backspace":
self._value = self._value[:max(0, self._cursor_index - 1)] + self._value[self._cursor_index:]
self.cursor_pos -= 1
self.trigger_event("changed", self._value)
elif key_name == "delete":
post_value = self._value[min(len(self._value), self._cursor_index + 1):]
self._value = self._value[:self._cursor_index] + post_value
self.cursor_pos = self._cursor_index
self.trigger_event("changed", self._value)
elif key_name == "arrow_left":
if event.get_modifier_state("alt") or event.get_modifier_state("ctrl"):
self.cursor_skip_left()
else:
self.cursor_pos -= 1
elif key_name == "arrow_right":
if event.get_modifier_state("alt") or event.get_modifier_state("ctrl"):
self.cursor_skip_right()
else:
self.cursor_pos += 1
elif key_name == "escape":
self.blur()
elif key_name == "home":
self.cursor_pos = 0
elif key_name == "end":
self.cursor_pos = len(self.value)
self.trigger_event(key_name, self._value)
def on_keyrepeat(self, event):
""" Internal keyrepeat handler """
self.on_keydown(event)
def on_textinput(self, event):
""" Internal textinput handler """
self._value = self._value[:self._cursor_index] + event.message + \
self._value[self._cursor_index:]
self.cursor_pos = self._cursor_index + len(event.message)
self.trigger_event("changed", self._value)
def on_blur(self, event):
""" Internal blur handler """
self._cursor.hide()
if len(self._value) < 1:
self._placeholder.show()
self._layout.color = (1, 1, 1, 1)
def _render_text(self):
""" Internal method to render the text """
self._text.set_text(self._value)
self._cursor.left = self._text.left + \
self._text.text_handle.get_char_pos(self._cursor_index) + 1
max_left = self.width - 15
if self._value:
self._placeholder.hide()
else:
if not self.focused:
self._placeholder.show()
# Scroll if the cursor is outside of the clip bounds
rel_pos = self.get_relative_pos(self._cursor.get_abs_pos()).x
if rel_pos >= max_left:
self._text_scroller.left = min(0, max_left - self._cursor.left)
if rel_pos <= 0:
self._text_scroller.left = min(0, - self._cursor.left - rel_pos)
def cursor_skip_left(self):
""" Moves the cursor to the left, skipping the previous word """
left_hand_str = ''.join(reversed(self.value[0:self.cursor_pos]))
match = self.re_skip.match(left_hand_str)
if match is not None:
self.cursor_pos -= match.end() - 1
else:
self.cursor_pos = 0
def cursor_skip_right(self):
""" Moves the cursor to the right, skipping the next word """
right_hand_str = self.value[self.cursor_pos:]
match = self.re_skip.match(right_hand_str)
if match is not None:
self.cursor_pos += match.end() - 1
else:
self.cursor_pos = len(self.value)

View File

@ -0,0 +1,8 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUIInputHandler as __LUIInputHandler
LUIInputHandler = __LUIInputHandler

77
Builtin/LUILabel.py Normal file
View File

@ -0,0 +1,77 @@
from panda3d.lui import LUIText
from LUIObject import LUIObject
from LUIInitialState import LUIInitialState
__all__ = ["LUILabel"]
class LUILabel(LUIObject):
""" A simple label, displaying text. """
# Default variables which can be overridden by skins
DEFAULT_COLOR = (0.9, 0.9, 0.9, 1)
DEFAULT_USE_SHADOW = True
def __init__(self, text="Label", shadow=None, font_size=14, font="label", color=None, wordwrap=False, **kwargs):
""" Creates a new label. If shadow is True, a small text shadow will be
rendered below the actual text. """
LUIObject.__init__(self)
LUIInitialState.init(self, kwargs)
self._text = LUIText(
self,
text,
font,
font_size,
0,
0,
wordwrap
)
self._text.z_offset = 1
if color is None:
self.color = LUILabel.DEFAULT_COLOR
else:
self.color = color
if shadow is None:
shadow = LUILabel.DEFAULT_USE_SHADOW
self._have_shadow = shadow
if self._have_shadow:
self._shadow_text = LUIText(
self,
text,
font,
font_size,
0,
0,
wordwrap
)
self._shadow_text.top = 1
self._shadow_text.color = (0,0,0,0.6)
def get_text_handle(self):
""" Returns a handle to the internal used LUIText object """
return self._text
text_handle = property(get_text_handle)
def get_text(self):
""" Returns the current text of the label """
return self._text.text
def set_text(self, text):
""" Sets the text of the label """
self._text.text = text
if self._have_shadow:
self._shadow_text.text = text
text = property(get_text, set_text)
def get_color(self):
""" Returns the current color of the label's text """
return self._text.color
def set_color(self, color):
""" Sets the color of the label's text """
self._text.color = color
color = property(get_color, set_color)

105
Builtin/LUILayouts.py Normal file
View File

@ -0,0 +1,105 @@
from __future__ import print_function, division
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUIHorizontalLayout import LUIHorizontalLayout
from LUIInitialState import LUIInitialState
__all__ = ["LUICornerLayout", "LUIHorizontalStretchedLayout"]
class LUICornerLayout(LUIObject):
""" This is a layout which is used to combine 9 sprites to a single sprite,
e.g. used for box shadow or frames."""
# List of all sprite identifiers required for the layout
_MODES = ["TR", "Top", "TL", "Right", "Mid", "Left", "BR", "Bottom", "BL"]
def __init__(self, image_prefix="", **kwargs):
""" Creates a new layout, using the image_prefix as prefix. """
LUIObject.__init__(self)
self.set_size("100%", "100%")
self._prefix = image_prefix
self._parts = {}
for i in self._MODES:
self._parts[i] = LUISprite(self, "blank", "skin")
self._update_layout()
LUIInitialState.init(self, kwargs)
def _update_layout(self):
""" Updates the layouts components. """
for i in self._MODES:
self._parts[i].set_texture(self._prefix + i, "skin", resize=True)
# Top and Left
self._parts["Top"].width = "100%"
self._parts["Top"].margin = (0, self._parts["TR"].width, 0, self._parts["TL"].width)
self._parts["Left"].height = "100%"
self._parts["Left"].margin = (self._parts["TL"].height, 0, self._parts["BL"].height, 0)
# Mid
self._parts["Mid"].set_size("100%", "100%")
self._parts["Mid"].margin = (self._parts["Top"].height, self._parts["Right"].width,
self._parts["Bottom"].height, self._parts["Left"].width)
# Bottom and Right
self._parts["Bottom"].width = "100%"
self._parts["Bottom"].margin = (0, self._parts["BR"].width, 0, self._parts["BL"].width)
self._parts["Bottom"].bottom = 0
self._parts["Right"].height = "100%"
self._parts["Right"].margin = (self._parts["TR"].height, 0, self._parts["BR"].width, 0)
self._parts["Right"].right = 0
# Corners
self._parts["TL"].top_left = 0, 0
self._parts["TR"].top_right = 0, 0
self._parts["BL"].bottom_left = 0, 0
self._parts["BR"].bottom_right = 0, 0
def set_prefix(self, prefix):
""" Changes the texture of the layout """
self._prefix = prefix
self._update_layout()
def get_prefix(self):
""" Returns the layouts texture prefix """
return self._prefix
prefix = property(get_prefix, set_prefix)
class LUIHorizontalStretchedLayout(LUIObject):
""" A layout which takes 3 sprites, a left sprite, a right sprite, and a
middle sprite. While the left and right sprites remain untouched, the middle
one will be stretched to fit the layout """
def __init__(self, parent=None, prefix="ButtonDefault", **kwargs):
LUIObject.__init__(self)
self._layout = LUIHorizontalLayout(self, spacing=0)
self._layout.width = "100%"
self._sprite_left = LUISprite(self._layout.cell(), "blank", "skin")
self._sprite_mid = LUISprite(self._layout.cell('*'), "blank", "skin")
self._sprite_right = LUISprite(self._layout.cell(), "blank", "skin")
if parent is not None:
self.parent = parent
self.prefix = prefix
LUIInitialState.init(self, kwargs)
def set_prefix(self, prefix):
""" Sets the layout prefix, this controls which sprites will be used """
self._sprite_left.set_texture(prefix + "_Left", "skin")
self._sprite_mid.set_texture(prefix, "skin")
self._sprite_right.set_texture(prefix + "_Right", "skin")
self._sprite_mid.width = "100%"
self._prefix = prefix
def get_prefix(self):
""" Returns the layout prefix """
return self._prefix
prefix = property(get_prefix, set_prefix)

18
Builtin/LUIObject.py Normal file
View File

@ -0,0 +1,18 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUIObject as _LUIObject
from LUIInitialState import LUIInitialState
__all__ = ["LUIObject"]
class LUIObject(_LUIObject):
""" This is a wrapper class for the C++ LUIObject class, to be able to
use it in a more convenient way """
def __init__(self, *args, **kwargs):
_LUIObject.__init__(self, *args)
LUIInitialState.init(self, kwargs)

72
Builtin/LUIProgressbar.py Normal file
View File

@ -0,0 +1,72 @@
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUILayouts import LUIHorizontalStretchedLayout
from LUILabel import LUILabel
class LUIProgressbar(LUIObject):
""" A simple progress bar """
def __init__(self, parent=None, width=200, value=50, show_label=True):
""" Constructs a new progress bar. If show_label is True, a label indicating
the current progress is shown """
LUIObject.__init__(self)
self.set_width(width)
self._bg_layout = LUIHorizontalStretchedLayout(
parent=self, prefix="ProgressbarBg", width="100%")
self._fg_left = LUISprite(self, "ProgressbarFg_Left", "skin")
self._fg_mid = LUISprite(self, "ProgressbarFg", "skin")
self._fg_right = LUISprite(self, "ProgressbarFg_Right", "skin")
self._fg_finish = LUISprite(self, "ProgressbarFg_Finish", "skin")
self._show_label = show_label
self._progress_pixel = 0
self._fg_finish.right = 0
if self._show_label:
self._progress_label = LUILabel(parent=self, text=u"33 %")
self._progress_label.centered = (True, True)
self.set_value(value)
self._update_progress()
if parent is not None:
self.parent = parent
def get_value(self):
""" Returns the current value of the progress bar """
return (self._progress_pixel / self.width * 100.0)
def set_value(self, val):
""" Sets the value of the progress bar """
val = max(0, min(100, val))
self._progress_pixel = int(val / 100.0 * self.width)
self._update_progress()
value = property(get_value, set_value)
def _update_progress(self):
""" Internal method to update the progressbar """
self._fg_finish.hide()
if self._progress_pixel <= self._fg_left.width + self._fg_right.width:
self._fg_mid.hide()
self._fg_right.left = self._fg_left.width
else:
self._fg_mid.show()
self._fg_mid.left = self._fg_left.width
self._fg_mid.width = self._progress_pixel - self._fg_right.width - self._fg_left.width
self._fg_right.left = self._fg_mid.left + self._fg_mid.width
if self._progress_pixel >= self.width - self._fg_right.width:
self._fg_finish.show()
self._fg_finish.right = - (self.width - self._progress_pixel)
self._fg_finish.clip_bounds = (0, self.width - self._progress_pixel, 0, 0)
if self._show_label:
percentage = self._progress_pixel / self.width * 100.0
self._progress_label.set_text("{} %".format(int(percentage)))

91
Builtin/LUIRadiobox.py Normal file
View File

@ -0,0 +1,91 @@
from __future__ import division
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUIInitialState import LUIInitialState
from LUILabel import LUILabel
class LUIRadiobox(LUIObject):
""" A radiobox which can be used in combination with a LUIRadioboxGroup """
def __init__(self, parent=None, group=None, value=None, active=False, label=u"Radiobox", **kwargs):
""" Constructs a new radiobox. group should be a handle to a LUIRadioboxGroup.
value will be the value returned by group.value, in case the box was
selected. By default, the radiobox is not active. """
assert group is not None, "LUIRadiobox needs a LUIRadioboxGroup!"
LUIObject.__init__(self, x=0, y=0, solid=True)
self._sprite = LUISprite(self, "Radiobox_Default", "skin")
self._label = LUILabel(parent=self, text=label, margin=(0, 0, 0, 23),
center_vertical=True)
self._value = value
self._active = False
self._hovered = False
self._group = group
self._group.register_box(self)
if active:
self.set_active()
if parent:
self.parent = parent
LUIInitialState.init(self, kwargs)
def on_click(self, event):
""" Internal onclick handler. Do not override. """
self.set_active()
def on_mouseover(self, event):
""" Internal mouseover handler """
self._hovered = True
self._update_sprite()
def on_mouseout(self, event):
""" Internal mouseout handler """
self._hovered = False
self._update_sprite()
def set_active(self):
""" Internal function to set the radiobox active """
if self._group is not None:
self._group.set_active_box(self)
else:
self._update_state(True)
def get_value(self):
""" Returns the value of the radiobox """
return self._value
def set_value(self, value):
""" Sets the value of the radiobox """
self._value = value
value = property(get_value, set_value)
def get_label(self):
""" Returns a handle to the label, so it can be modified (e.g. change
its text) """
return self._label
label = property(get_label)
def _update_state(self, active):
""" Internal method to update the state of the radiobox. Called by the
LUIRadioboxGroup """
self._active = active
self.trigger_event("changed")
self._update_sprite()
def on_mousedown(self, event):
""" Internal onmousedown handler. Do not override. """
self._sprite.color = (0.86,0.86,0.86,1.0)
def on_mouseup(self, event):
""" Internal onmouseup handler. Do not override. """
self._sprite.color = (1,1,1,1)
def _update_sprite(self):
""" Internal function to update the sprite of the radiobox """
img = "Radiobox_Active" if self._active else "Radiobox_Default"
if self._hovered:
img += "Hover"
self._sprite.set_texture(img, "skin")

View File

@ -0,0 +1,41 @@
from LUIObject import LUIObject
class LUIRadioboxGroup(LUIObject):
""" Simple helper to group a bunch of LUIRadiobox and ensure only one is
checked at one timem """
def __init__(self):
""" Constructs a new group without any radioboxes inside """
self._boxes = []
self._selected_box = None
def register_box(self, box):
""" Registers a box to the collection """
if box not in self._boxes:
self._boxes.append(box)
def set_active_box(self, active_box):
""" Internal function to set the active box """
for box in self._boxes:
if box is not active_box:
box._update_state(False)
else:
box._update_state(True)
self._selected_box = active_box
def get_active_box(self):
""" Returns the current selected box """
return self._selected_box
active_box = property(get_active_box, set_active_box)
def get_active_value(self):
""" Returns the value of the current selected box (or None if none is
selected) """
if self._selected_box is None:
return None
return self._selected_box.get_value()
active_value = property(get_active_value)

8
Builtin/LUIRegion.py Normal file
View File

@ -0,0 +1,8 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUIRegion as __LUIRegion
LUIRegion = __LUIRegion

8
Builtin/LUIRoot.py Normal file
View File

@ -0,0 +1,8 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUIRoot as __LUIRoot
LUIRoot = __LUIRoot

View File

@ -0,0 +1,155 @@
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUIInitialState import LUIInitialState
from LUILayouts import LUIHorizontalStretchedLayout
class LUIScrollableRegion(LUIObject):
""" Scrollable region, reparent elements to the .content_node to make them
scroll. """
def __init__(self, parent=None, width=100, height=100, padding=10, **kwargs):
LUIObject.__init__(self)
self.set_size(width, height)
self._content_parent = LUIObject(self)
self._content_parent.set_size("100%", "100%")
self._content_parent.clip_bounds = (0,0,0,0)
self._content_clip = LUIObject(self._content_parent, x=padding, y=padding)
self._content_clip.set_size("100%", "100%")
self._content_scroller = LUIObject(self._content_clip)
self._content_scroller.width = "100%"
self._scrollbar = LUIObject(self, x=0, y=0, w=20)
self._scrollbar.height = "100%"
self._scrollbar.right = -10
self._scrollbar_bg = LUISprite(self._scrollbar, "blank", "skin")
self._scrollbar_bg.color = (1,1,1,0.05)
self._scrollbar_bg.set_size(3, "100%")
self._scrollbar_bg.center_horizontal = True
# Handle
self._scrollbar_handle = LUIObject(self._scrollbar, x=5, y=0, w=10)
self._scroll_handle_top = LUISprite(self._scrollbar_handle, "ScrollbarHandle_Top", "skin")
self._scroll_handle_mid = LUISprite(self._scrollbar_handle, "ScrollbarHandle", "skin")
self._scroll_handle_bottom = LUISprite(self._scrollbar_handle, "ScrollbarHandle_Bottom", "skin")
self._scrollbar_handle.solid = True
self._scrollbar.solid = True
self._scrollbar_handle.bind("mousedown", self._start_scrolling)
self._scrollbar_handle.bind("mouseup", self._stop_scrolling)
self._scrollbar.bind("mousedown", self._on_bar_click)
self._scrollbar.bind("mouseup", self._stop_scrolling)
self._handle_dragging = False
self._drag_start_y = 0
self._scroll_top_position = 0
self._content_height = 400
# Scroll shadow
self._scroll_shadow_top = LUIHorizontalStretchedLayout(parent=self, prefix="ScrollShadowTop", width="100%")
self._scroll_shadow_bottom = LUIHorizontalStretchedLayout(parent=self, prefix="ScrollShadowBottom", width="100%")
self._scroll_shadow_bottom.bottom = 0
self._handle_height = 100
if parent is not None:
self.parent = parent
LUIInitialState.init(self, kwargs)
self.content_node = self._content_scroller
taskMgr.doMethodLater(0.05, lambda task: self._update(), "update_scrollbar")
def _on_bar_click(self, event):
""" Internal handler when the user clicks on the scroll bar """
self._scroll_to_bar_pixels(event.coordinates.y - self._scrollbar.abs_pos.y - self._handle_height / 2.0)
self._update()
self._start_scrolling(event)
def _start_scrolling(self, event):
""" Internal method when we start scrolling """
self.request_focus()
if not self._handle_dragging:
self._drag_start_y = event.coordinates.y
self._handle_dragging = True
def _stop_scrolling(self, event):
""" Internal handler when we should stop scrolling """
if self._handle_dragging:
self._handle_dragging = False
self.blur()
def _scroll_to_bar_pixels(self, pixels):
""" Internal method to convert from pixels to a relative position """
offset = pixels * self._content_height / self.height
self._scroll_top_position = offset
self._scroll_top_position = max(0, min(self._content_height - self._content_clip.height, self._scroll_top_position))
def on_tick(self, event):
""" Internal on tick handler """
if self._handle_dragging:
scroll_abs_pos = self._scrollbar.abs_pos
clamped_coord_y = max(scroll_abs_pos.y, min(scroll_abs_pos.y + self.height, event.coordinates.y))
offset = clamped_coord_y - self._drag_start_y
self._drag_start_y = clamped_coord_y
self._scroll_to_bar_pixels(self._scroll_top_position/self._content_height*self.height + offset)
self._update()
def _set_handle_height(self, height):
""" Internal method to set the scrollbar height """
self._scroll_handle_mid.top = float(self._scroll_handle_top.height)
self._scroll_handle_mid.height = max(0.0, height - self._scroll_handle_top.height - self._scroll_handle_bottom.height)
self._scroll_handle_bottom.top = self._scroll_handle_mid.height + self._scroll_handle_mid.top
self._handle_height = height
def _update(self):
""" Internal method to update the scroll bar """
self._content_height = max(1, self._content_scroller.get_height() + 20)
self._content_scroller.top = -self._scroll_top_position
scrollbar_height = max(0.1, min(1.0, self._content_clip.height / self._content_height))
scrollbar_height_px = scrollbar_height * self.height
self._set_handle_height(scrollbar_height_px)
self._scrollbar_handle.top = self._scroll_top_position / self._content_height * self.height
top_alpha = max(0.0, min(1.0, self._scroll_top_position / 50.0))
bottom_alpha = max(0.0, min(1.0, (self._content_height - self._scroll_top_position - self._content_clip.height) / 50.0 ))
self._scroll_shadow_top.color = (1,1,1,top_alpha)
self._scroll_shadow_bottom.color = (1,1,1,bottom_alpha)
if self._content_height <= self.height:
self._scrollbar_handle.hide()
else:
self._scrollbar_handle.show()
def on_element_added(self):
taskMgr.doMethodLater(0.05, lambda task: self._update(), "update_layout")
def get_scroll_percentage(self):
""" Returns the current scroll height in percentage from 0 to 1 """
return self._scroll_top_position / max(1, self._content_height - self._content_clip.height)
def set_scroll_percentage(self, percentage):
""" Sets the scroll position in percentage, 0 means top and 1 means bottom """
percentage = max(0.0, min(1.0, percentage))
pixels = max(0.0, self._content_height - self._content_clip.height) * percentage
self._scroll_top_position = pixels
self._update()
scroll_percentage = property(get_scroll_percentage, set_scroll_percentage)
def scroll_to_bottom(self):
""" Scrolls to the bottom of the frame """
taskMgr.doMethodLater(0.07, lambda task: self.set_scroll_percentage(1.0), "scroll_to_bottom")
def scroll_to_top(self):
""" Scrolls to the top of the frame """
taskMgr.doMethodLater(0.07, lambda task: self.set_scroll_percentage(0.0), "scroll_to_top")

207
Builtin/LUISelectbox.py Normal file
View File

@ -0,0 +1,207 @@
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUILabel import LUILabel
from LUILayouts import LUICornerLayout, LUIHorizontalStretchedLayout
from LUIInitialState import LUIInitialState
from functools import partial
__all__ = ["LUISelectbox"]
class LUISelectbox(LUIObject):
""" Selectbox widget, showing several options whereas the user can select
only one. """
def __init__(self, width=200, options=None, selected_option=None, **kwargs):
""" Constructs a new selectbox with a given width """
LUIObject.__init__(self, x=0, y=0, w=width+4, solid=True)
LUIInitialState.init(self, kwargs)
# The selectbox has a small border, to correct this we move it
self.margin.left = -2
self._bg_layout = LUIHorizontalStretchedLayout(parent=self, prefix="Selectbox", width="100%")
self._label_container = LUIObject(self, x=10, y=0)
self._label_container.set_size("100%", "100%")
self._label_container.clip_bounds = (0,0,0,0)
self._label = LUILabel(parent=self._label_container, text=u"Select an option ..")
self._label.center_vertical = True
self._drop_menu = LUISelectdrop(parent=self, width=width)
self._drop_menu.top = self._bg_layout._sprite_right.height - 7
self._drop_menu.topmost = True
self._drop_open = False
self._drop_menu.hide()
self._options = []
self._current_option_id = None
if options is not None:
self._options = options
self._select_option(selected_option)
def get_selected_option(self):
""" Returns the selected option """
return self._current_option_id
def set_selected_option(self, option_id):
""" Sets the selected option """
raise NotImplementedError()
selected_option = property(get_selected_option, set_selected_option)
def _render_options(self):
""" Internal method to render all available options """
self._drop_menu._render_options(self._options)
def get_options(self):
""" Returns the list of options """
return self._options
def set_options(self, options):
""" Sets the list of options, options should be a list containing entries
whereas each entry is a tuple in the format (option_id, option_label).
The option ID can be an arbitrary object, and will not get modified. """
self._options = options
self._current_option_id = None
self._render_options()
options = property(get_options, set_options)
def _select_option(self, opt_id):
""" Internal method to select an option """
self._label.alpha = 1.0
for elem_opt_id, opt_val in self._options:
if opt_id == elem_opt_id:
self._label.text = opt_val
self._current_option_id = opt_id
return
self._label.alpha = 0.3
# def on_mouseover(self, event):
# """ Internal handle when the select-knob was hovered """
# self._bg_layout.color = (0.9,0.9,0.9,1.0)
# def on_mouseout(self, event):
# """ Internal handle when the select-knob was no longer hovered """
# self._bg_layout.color = (1,1,1,1.0)
def on_click(self, event):
""" On-Click handler """
self.request_focus()
if self._drop_open:
self._close_drop()
else:
self._open_drop()
def on_mousedown(self, event):
""" Mousedown handler """
self._bg_layout.alpha = 0.9
def on_mouseup(self, event):
""" Mouseup handler """
self._bg_layout.alpha = 1
def on_blur(self, event):
""" Internal handler when the selectbox lost focus """
if not self._drop_menu.focused:
self._close_drop()
def _open_drop(self):
""" Internal method to show the dropdown menu """
if not self._drop_open:
self._render_options()
self._drop_menu.show()
self.request_focus()
self._drop_open = True
def _close_drop(self):
""" Internal method to close the dropdown menu """
if self._drop_open:
self._drop_menu.hide()
self._drop_open = False
def _on_option_selected(self, opt_id):
""" Internal method when an option got selected """
self._select_option(opt_id)
self._close_drop()
class LUISelectdrop(LUIObject):
""" Internal class used by the selectbox, representing the dropdown menu """
def __init__(self, parent, width=200):
LUIObject.__init__(self, x=0, y=0, w=width, h=1, solid=True)
self._layout = LUICornerLayout(parent=self, image_prefix="Selectdrop_",
width=width + 10, height=100)
self._layout.margin.left = -3
self._opener = LUISprite(self, "SelectboxOpen_Right", "skin")
self._opener.right = -4
self._opener.top = -25
self._opener.z_offset = 3
self._container = LUIObject(self._layout, 0, 0, 0, 0)
self._container.width = self.width
self._container.clip_bounds = (0,0,0,0)
self._container.left = 5
self._container.solid = True
self._container.bind("mousedown", lambda *args: self.request_focus())
self._selectbox = parent
self._option_focus = False
self.parent = self._selectbox
def _on_opt_over(self, event):
""" Inernal handler when an option got hovered """
event.sender.color = (0,0,0,0.1)
def _on_opt_out(self, event):
""" Inernal handler when an option got no longer hovered """
event.sender.color = (0,0,0,0)
def _on_opt_click(self, opt_id, event):
""" Internal handler when an option got clicked """
self._selectbox._on_option_selected(opt_id)
def _render_options(self, options):
""" Internal method to update the options """
num_visible_options = min(30, len(options))
offset_top = 6
self._layout.height = num_visible_options * 30 + offset_top + 11
self._container.height = num_visible_options * 30 + offset_top + 1
self._container.remove_all_children()
current_y = offset_top
for opt_id, opt_val in options:
opt_container = LUIObject(self._container, x=0, y=current_y, w=self._container.width - 30, h=30)
opt_bg = LUISprite(opt_container, "blank", "skin")
opt_bg.width = self._container.width
opt_bg.height = opt_container.height
opt_bg.color = (0,0,0,0)
opt_bg.bind("mouseover", self._on_opt_over)
opt_bg.bind("mouseout", self._on_opt_out)
opt_bg.bind("mousedown", lambda *args: self.request_focus())
opt_bg.bind("click", partial(self._on_opt_click, opt_id))
opt_bg.solid = True
opt_label = LUILabel(parent=opt_container, text=opt_val)
opt_label.top = 8
opt_label.left = 8
if opt_id == self._selectbox.selected_option:
opt_label.color = (0.6, 0.9, 0.4, 1.0)
divider = LUISprite(opt_container, "SelectdropDivider", "skin")
divider.top = 30 - divider.height / 2
divider.width = self._container.width
current_y += 30

53
Builtin/LUISkin.py Normal file
View File

@ -0,0 +1,53 @@
import os
from os.path import join
from panda3d.core import Filename
from panda3d.lui import LUIFontPool, LUIAtlasPool
class LUISkin:
""" Abstract class, each skin derives from this class """
skin_location = ""
def __init__(self):
pass
def load(self):
""" Skins should override this. Each skin should at least provide the fonts
'default' and 'label', and at least one atlas named 'skin' """
raise NotImplementedError()
def get_resource(self, pth):
""" Turns a relative path into an absolute one, using the skin_location """
return Filename.from_os_specific(join(self.skin_location, pth)).get_fullpath()
class LUIDefaultSkin(LUISkin):
""" The default skin which comes with LUI """
skin_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../Skins/Default/")
def __init__(self):
pass
def load(self):
LUIFontPool.get_global_ptr().register_font(
"default", loader.loadFont(self.get_resource("font/SourceSansPro-Semibold.ttf")))
labelFont = loader.loadFont(self.get_resource("font/SourceSansPro-Semibold.ttf"))
labelFont.setPixelsPerUnit(32)
LUIFontPool.get_global_ptr().register_font(
"label", labelFont)
headerFont = loader.loadFont(self.get_resource("font/SourceSansPro-Light.ttf"))
headerFont.setPixelsPerUnit(80)
LUIFontPool.get_global_ptr().register_font("header", headerFont)
LUIAtlasPool.get_global_ptr().load_atlas("skin",
self.get_resource("res/atlas.txt"),
self.get_resource("res/atlas.png"))

131
Builtin/LUISlider.py Normal file
View File

@ -0,0 +1,131 @@
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUIInitialState import LUIInitialState
from LUILayouts import LUIHorizontalStretchedLayout
class LUISlider(LUIObject):
""" Slider which can be used to control values """
def __init__(self, parent=None, filled=True, min_value=0.0, max_value=1.0, width=100.0, value=None, **kwargs):
""" Constructs a new slider. If filled is True, the part behind the knob
will be solid """
LUIObject.__init__(self, x=0, y=0, solid=True)
self.set_width(width)
self._knob = LUISprite(self, "SliderKnob", "skin")
self._knob.z_offset = 2
self._knob.solid = True
# Construct the background
self._slider_bg = LUIHorizontalStretchedLayout(parent=self, prefix="SliderBg", center_vertical=True, width="100%", margin=(-1, 0, 0, 0))
self._filled = filled
self._min_value = min_value
self._max_value = max_value
self._side_margin = self._knob.width / 4
self._effective_width = self.width - 2 * self._side_margin
if self._filled:
self._slider_fill = LUIObject(self)
self._fill_left = LUISprite(self._slider_fill, "SliderBgFill_Left", "skin")
self._fill_mid = LUISprite(self._slider_fill, "SliderBgFill", "skin")
self._fill_mid.left = self._fill_left.width
self._slider_fill.z_offset = 1
self._slider_fill.center_vertical = True
if parent is not None:
self.parent = parent
# Handle various events
self._knob.bind("mousedown", self._start_drag)
self._knob.bind("mousemove", self._update_drag)
self._knob.bind("mouseup", self._stop_drag)
self._knob.bind("keydown", self._on_keydown)
self._knob.bind("blur", self._stop_drag)
self._knob.bind("keyrepeat", self._on_keydown)
self._drag_start_pos = None
self._dragging = False
self._drag_start_val = 0
self.current_val = 10
# Set initial value
if value is None:
self.set_value( (self._min_value + self._max_value) / 2.0 )
else:
self.set_value(value)
self._update_knob()
LUIInitialState.init(self, kwargs)
def on_click(self, event):
""" Internal on click handler """
# I don't like this behaviour
# relative_pos = self.get_relative_pos(event.coordinates)
# if not self._dragging:
# self._set_current_val(relative_pos.x)
def _update_knob(self):
""" Internal method to update the slider knob """
self._knob.left = self.current_val - (self._knob.width / 2) + self._side_margin
if self._filled:
self._fill_mid.width = self.current_val - self._fill_left.width + self._side_margin
def _set_current_val(self, pixels):
""" Internal method to set the current value in pixels """
pixels = max(0, min(self._effective_width, pixels))
self.current_val = pixels
self.trigger_event("changed")
self._update_knob()
def _start_drag(self, event):
""" Internal drag start handler """
self._knob.request_focus()
if not self._dragging:
self._drag_start_pos = event.coordinates
self._dragging = True
self._drag_start_val = self.current_val
self._knob.color = (0.8,0.8,0.8,1.0)
def set_value(self, value):
""" Sets the value of the slider, should be between minimum and maximum. """
scaled = (float(value) - float(self._min_value)) \
/ (float(self._max_value) - float(self._min_value)) \
* self._effective_width
self._set_current_val(scaled)
def get_value(self):
""" Returns the current value of the slider """
return (self.current_val / float(self._effective_width)) \
* (float(self._max_value) - float(self._min_value)) \
+ self._min_value
value = property(get_value, set_value)
def _on_keydown(self, event):
""" Internal keydown handler """
if event.message == "arrow_right":
self._set_current_val(self.current_val + 2)
elif event.message == "arrow_left":
self._set_current_val(self.current_val - 2)
elif event.message == "escape":
self.current_val = self._drag_start_val
self._stop_drag(event)
self._update_knob()
def _update_drag(self, event):
""" Internal drag handler """
if self._dragging:
dragOffset = event.coordinates.x - self._drag_start_pos.x
finalValue = self._drag_start_val + dragOffset
self._set_current_val(finalValue)
def _stop_drag(self, event):
""" Internal drag stop handelr """
self._drag_start_pos = None
self._dragging = False
self._drag_start_val = self.current_val
self._knob.color = (1,1,1,1)

18
Builtin/LUISprite.py Normal file
View File

@ -0,0 +1,18 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUISprite as _LUISprite
from LUIInitialState import LUIInitialState
__all__ = ["LUISprite"]
class LUISprite(_LUISprite):
""" This is a wrapper class for the C++ LUISprite class, to be able to
use it in a more convenient way """
def __init__(self, *args, **kwargs):
_LUISprite.__init__(self, *args)
LUIInitialState.init(self, kwargs)

View File

@ -0,0 +1,30 @@
from LUIObject import LUIObject
from LUISprite import LUISprite
from LUIInitialState import LUIInitialState
class LUISpriteButton(LUIObject):
""" Simple button that uses only two images: Default and focus. """
def __init__(self, template="ButtonDefault", **kwargs):
LUIObject.__init__(self, x=0, y=0, solid=True)
self._template = template
self._button_sprite = LUISprite(self, template, "skin")
if 'width' in kwargs:
self._button_sprite.width = kwargs['width']
if 'height' in kwargs:
self._button_sprite.height = kwargs['height']
LUIInitialState.init(self, kwargs)
def on_mousedown(self, event):
""" Internal on_mousedown handler. Do not override """
self._button_sprite.set_texture(self.template + "Focus", "skin", resize=False)
def on_mouseup(self, event):
""" Internal on_mouseup handler. Do not override """
self._button_sprite.set_texture(self.template, "skin", resize=False)
def on_click(self, event):
""" Internal onclick handler. Do not override """
self.trigger_event("changed")

87
Builtin/LUITabbedFrame.py Normal file
View File

@ -0,0 +1,87 @@
from LUIFrame import LUIFrame
from LUILabel import LUILabel
from LUIObject import LUIObject
from LUIVerticalLayout import LUIVerticalLayout
from LUIHorizontalLayout import LUIHorizontalLayout
class LUITabbedFrame(LUIFrame):
def __init__(self, **kwargs):
super(LUITabbedFrame, self).__init__(**kwargs)
# The main window layout
bar_spacing = kwargs.get('bar_spacing', 3)
self.root_layout = LUIVerticalLayout(parent = self,
spacing = bar_spacing)
self.root_layout.height = "100%"
self.root_layout.width = "100%"
self.root_layout.margin = 0
# The header bar
header_spacing = kwargs.get('header_spacing', 3)
self.header_bar = LUIHorizontalLayout(parent = self.root_layout.cell("?"),
spacing = header_spacing)
self.root_layout.add(self.header_bar, "?")
self.header_to_frame = {}
self.current_frame = None
# The main window contents
self.main_frame = LUIObject()
self.main_frame.height = "100%"
self.main_frame.width = "100%"
self.main_frame.margin = 0
# self.main_frame.padding = 0
self.root_layout.add(self.main_frame, "*")
self.bind("expose", self.on_expose)
self.bind("unexpose", self.on_unexpose)
def add(self, header, frame):
# header
if isinstance(header, str):
header = LUILabel(text = header)
self.header_bar.add(header, "?")
self.header_to_frame[header] = frame
header.solid = True
header.bind("click", self._change_to_tab)
# Frame
frame.parent = self.main_frame
frame.width = "100%"
frame.height = "100%"
# Put frame in front
if self.current_frame is None:
self.current_frame = frame
self.current_frame.show()
else:
frame.hide()
return header
def _find_header_index(self, header):
for idx, child in enumerate(self.header_bar.children):
if any([grandchild == header for grandchild in child.children]):
break
else:
raise ValueError("Given object is not a header bar item.")
return idx
def remove(self, header):
idx = self._find_header_index(header)
self.header_bar.remove_cell(idx)
frame = self.header_to_frame[header]
frame.parent = None
del self.header_to_frame[header]
if self.current_frame == frame:
self.current_frame = None
def _change_to_tab(self, lui_event):
header = lui_event.sender
if self.current_frame is not None:
self.current_frame.trigger_event("unexpose")
self.current_frame.hide()
self.current_frame = self.header_to_frame[header]
self.current_frame.show()
self.current_frame.trigger_event("expose")
def on_expose(self, event):
self.current_frame.trigger_event("expose")
def on_unexpose(self, event):
self.current_frame.trigger_event("unexpose")

View File

@ -0,0 +1,20 @@
"""
This is a wrapper file. It contains no actual implementation
"""
from panda3d.lui import LUIVerticalLayout as _LUIVerticalLayout
from LUIInitialState import LUIInitialState
__all__ = ["LUIVerticalLayout"]
class LUIVerticalLayout(_LUIVerticalLayout):
""" This is a wrapper class for the C++ LUIVerticalLayout class, to be
able to use it in a more convenient way. It leverages LUIInitialState
to be able to pass arbitrary keyword arguments. """
def __init__(self, parent=None, spacing=0.0, **kwargs):
_LUIVerticalLayout.__init__(self, parent, spacing)
LUIInitialState.init(self, kwargs)

0
Builtin/__init__.py Normal file
View File

1
Demos/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Test.py

View File

@ -0,0 +1,56 @@
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
# Load some builtin LUI classes. When lui is included in panda, this will be
# from direct.lui.LUIButton import LUIButton
from Builtin.LUIButton import LUIButton
from Builtin.LUISkin import LUIDefaultSkin
from Builtin.LUIRegion import LUIRegion
from Builtin.LUIInputHandler import LUIInputHandler
# Setup panda, nothing special here
from panda3d.core import load_prc_file_data
load_prc_file_data("", """
text-minfilter linear
text-magfilter linear
text-pixels-per-unit 32
sync-video #f
textures-power-2 none
show-frame-rate-meter #t
win-size 700 600
window-title LUI Minimal Example
""")
from direct.showbase.ShowBase import ShowBase
class Application(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# Construct a new LUIRegion
region = LUIRegion.make("LUI", base.win)
# Construct a new InputHandler to catch and process events
handler = LUIInputHandler()
base.mouseWatcher.attach_new_node(handler)
region.set_input_handler(handler)
# Load the default LUI skin
skin = LUIDefaultSkin()
skin.load()
# LUI is initialized now, so we can start adding elements, for example:
button = LUIButton(parent=region.root, text="Hello world!", top=30, left=30)
Application().run()

107
Demos/02_SimpleConsole.py Normal file
View File

@ -0,0 +1,107 @@
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from direct.showbase.ShowBase import ShowBase
from direct.showbase.DirectObject import DirectObject
from panda3d.core import *
load_prc_file_data("", """
notify-level-lui info
text-minfilter linear
text-magfilter linear
text-pixels-per-unit 32
sync-video #f
textures-power-2 none
show-frame-rate-meter #t
win-size 780 630
window-title LUI Demo
win-fixed-size #f
""")
# Imports
import codecs
from LUISkin import LUIDefaultSkin
from LUIFrame import LUIFrame
from LUILabel import LUILabel
from LUIInputField import LUIInputField
from LUIFormattedLabel import LUIFormattedLabel
from LUIScrollableRegion import LUIScrollableRegion
from LUIObject import LUIObject
from LUIRegion import LUIRegion
from LUIInputHandler import LUIInputHandler
from LUIVerticalLayout import LUIVerticalLayout
from Skins.Metro.LUIMetroSkin import LUIMetroSkin
s = ShowBase()
# Load a LUI Skin
if False:
skin = LUIMetroSkin()
base.win.set_clear_color(Vec4(1))
else:
skin = LUIDefaultSkin()
base.win.set_clear_color(Vec4(0.1, 0.0, 0.0, 1))
skin.load()
# Initialize LUI
region = LUIRegion.make("LUI", base.win)
handler = LUIInputHandler()
base.mouseWatcher.attach_new_node(handler)
region.set_input_handler(handler)
# Title
title_label = LUILabel(parent=region.root, text="LUI Console Example", font_size=40,
font="header", pos=(25, 17))
# Container
container = LUIFrame(parent = region.root, width=700, height=500,
style=LUIFrame.FS_sunken, margin=30, top=50)
text_container = LUIScrollableRegion(parent=container, width=675, height=440,
padding=0)
base.win.set_clear_color(Vec4(0.1, 0.1, 0.1, 1.0))
layout = LUIVerticalLayout(parent=text_container.content_node)
def send_command(event):
""" Called when the user presses enter in the input field, submits the
command and prints something on the console """
label = LUIFormattedLabel()
color = (0.9, 0.9, 0.9, 1.0)
if event.message.startswith(u"/"):
color = (0.35, 0.65, 0.24, 1.0)
label.add(text=">>> ", color=(0.35, 0.65, 0.24, 1.0))
label.add(text=event.message, color=color)
layout.add(label)
result = LUIFormattedLabel()
result.add("Your command in rot13: " + codecs.encode(event.message, "rot13"), color=(0.4, 0.4, 0.4, 1.0))
layout.add(result)
input_field.clear()
text_container.scroll_to_bottom()
# Create the input box
input_field = LUIInputField(parent=container, bottom=0, left=0, width="100%")
input_field.bind("enter", send_command)
input_field.request_focus()
# Add some initial commands
for demo_command in ["Hello world!", "This is a simple console", "You can type commands like this:", "/test"]:
input_field.trigger_event("enter", demo_command)
s.run()

103
Demos/B_BlockText.py Normal file
View File

@ -0,0 +1,103 @@
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from DemoFramework import DemoFramework
from LUILabel import LUILabel
from LUIBlockText import LUIBlockText
from LUIScrollableRegion import LUIScrollableRegion
import random
f = DemoFramework()
f.prepare_demo("LUIBlockText")
# Constructor
f.add_constructor_parameter("text", "u'Label'")
f.add_constructor_parameter("shadow", "True")
f.add_constructor_parameter("font_size", "14")
f.add_constructor_parameter("font", "'label'")
# Functions
f.add_public_function("clear", [])
f.add_public_function("set_text", [("text", "string")])
f.add_public_function("set_wrap", [("wrap", "boolean")])
f.add_public_function("set_width", [("width", "integer")])
f.add_property("labels", "list")
# Events
f.construct_sourcecode("LUIBlockText")
text_container = LUIScrollableRegion(
parent=f.get_widget_node(),
width=340,
height=190,
padding=0,
)
#TODO: Support newline through charcode 10
#TODO: If space causes next line, dont print it
# Create a new label
label = LUIBlockText(parent=text_container, width=310)
# Paragraph with no line breaks
label.add(
text='''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada sit amet erat non gravida. Pellentesque sit amet cursus risus Sed egestas, nulla in tempor cursus, ante felis cursus magna, nec vehicula nisi nulla eu nulla.''',
color=(0.9,0.9,.9),
wordwrap=True,
padding=5,
)
# Paragraph with some linebreaks
label.add(
text='''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada sit amet erat non gravida.
Pellentesque sit amet cursus risus Sed egestas, nulla in tempor cursus, ante felis cursus magna, nec vehicula nisi nulla eu nulla.
Nulla sed pellentesque erat. Morbi facilisis at erat id auctor. Phasellus euismod facilisis sem, at molestie velit condimentum sit amet.
Nulla posuere rhoncus aliquam.''',
color=(0.9,0.9,.9),
wordwrap=True,
padding=5,
)
# Paragraph with no spaces or linebreaks
label.add(
text='''Loremipsumolorsitamet,consecteturadipiscingelit.Sedmalesuadasitameteratnongravida.PellentesquesitametcursusrisusSedegestas,nullaintemporcursus,antefeliscursusmagna,necvehiculanisinullaeunulla.''',
color=(0.9,0.9,.9),
wordwrap=True,
padding=5,
)
def setWidth(width):
label.set_width(width)
text_container.on_element_added()
def setWrap(wrap):
label.set_wrap(wrap)
text_container.on_element_added()
f.set_actions({
"Set Random Text": lambda: label.set_text(str(random.randint(100, 10000))),
"Set Random Color": lambda: label.set_color((random.random(), random.random(), random.random(), 1)),
"Clear": lambda: label.clear(),
"Smaller": lambda: setWidth(200),
"Larger": lambda: setWidth(310),
"Wrapping on": lambda: setWrap(True),
"Wrapping off": lambda: setWrap(False),
})
base.run()

64
Demos/B_Button.py Normal file
View File

@ -0,0 +1,64 @@
"""
This file shows the smallest working LUI example, on which you can base your
LUI projects.
"""
# Add lui modules to the path. This will not be required when LUI is included
# in panda.
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
# Make sure the compiled lui.pyd and Builtin helpers can be found
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
# Load Panda3D core first so the dll dependencies for lui are present, then
# register the bundled lui module as panda3d.lui (expected by wrappers).
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from DemoFramework import DemoFramework
from LUIButton import LUIButton
from LUIHorizontalLayout import LUIHorizontalLayout
import random
f = DemoFramework()
f.prepare_demo("LUIButton")
# Constructor
f.add_constructor_parameter("text", "u'Button'")
f.add_constructor_parameter("template", "'ButtonDefault'")
# Functions
f.add_public_function("set_text", [("text", "string")])
f.add_public_function("get_text", [], "string")
f.add_property("text", "string")
# Construct source code
f.construct_sourcecode("LUIButton")
# Create 2 new buttons, and store them in a vertical layout
layout = LUIHorizontalLayout(parent=f.get_widget_node(), spacing=10)
button1 = LUIButton(text="Do not click me")
button2 = LUIButton(text="Instead click me", template="ButtonGreen")
layout.add(button1)
layout.add(button2)
def set_btn_text(text):
button1.text = text
f.set_actions({
"Set Random Text": lambda: set_btn_text("Text: " + str(random.randint(100, 10000000))),
})
run()

49
Demos/B_Checkbox.py Normal file
View File

@ -0,0 +1,49 @@
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from DemoFramework import DemoFramework
from LUICheckbox import LUICheckbox
import random
f = DemoFramework()
f.prepare_demo("LUICheckbox")
# Constructor
f.add_constructor_parameter("checked", "False")
f.add_constructor_parameter("label", "'Checkbox'")
# Functions
f.add_public_function("get_checked", [], "bool")
f.add_public_function("toggle_checked", [], "bool")
f.add_public_function("set_checked", [("checked", "bool")])
f.add_public_function("get_label", [], "UILabel")
f.add_property("checked", "bool")
f.add_property("label", "LUILabel")
# Events
f.add_event("changed")
f.construct_sourcecode("LUICheckbox")
# Create the checkbox
checkbox = LUICheckbox(parent=f.get_widget_node())
f.set_actions({
"Set Checked": lambda: checkbox.set_checked(True),
"Set Unchecked": lambda: checkbox.set_checked(False),
"Toggle Checked": lambda: checkbox.toggle_checked(),
"Set Random Text": lambda: checkbox.get_label().set_text("Text: " + str(random.randint(100, 10000))),
})
run()

58
Demos/B_FormattedLabel.py Normal file
View File

@ -0,0 +1,58 @@
from DemoFramework import DemoFramework
from LUIFormattedLabel import LUIFormattedLabel
import random
f = DemoFramework()
f.prepare_demo("LUIFormattedLabel")
# Functions
f.add_public_function("clear", [], "void")
f.add_public_function("newline", [], "void")
f.add_public_function("add", [("*args", "List"), ("**kwargs", "Dict")])
# Events
f.construct_sourcecode("LUIFormattedLabel")
# Create a new label
label = LUIFormattedLabel(parent=f.get_widget_node())
# Add parts to the label
label.add(text="Hello ", color=(0.2,0.6,1.0))
label.add(text="World", color=(1.0,0.6,0.2))
label.add(text="! ")
label.add(text="This ", font_size=20, margin=(-6, 0, 0, 0), color=(0.4,0.2,1.0))
label.add(text="is ", color=(1.0,0.2,1.0))
label.add(text="a formatted ", font_size=10, color=(0.6,0.3,0.6))
label.add(text="Label", font_size=25, margin =(-11, 0, 0, 0), color=(0.2,1.0,0.6))
# Go to next line
label.newline()
label.newline()
# Add some more parts
label.add(text="This is the same label ..", color=(0.3,0.7,0.32))
# Go to next line
label.newline()
label.newline()
# Add some more parts
label.add(text="... but another line forced with ", color=(0.6,0.3,0.8))
label.add(text="newline() ", color=(1.0,0.6,0.2))
def make_random_color():
return (random.random(), random.random(), random.random())
def newline():
label.newline()
label.add(text="New Line!", color=make_random_color())
f.set_actions({
"Add random text": lambda: label.add(text="Text ", color=make_random_color()),
"Go to next line": newline
})
run()

43
Demos/B_Frame.py Normal file
View File

@ -0,0 +1,43 @@
from DemoFramework import DemoFramework
from LUIVerticalLayout import LUIVerticalLayout
from LUIFrame import LUIFrame
from LUILabel import LUILabel
from LUIButton import LUIButton
from LUIObject import LUIObject
import random
f = DemoFramework()
f.prepare_demo("LUIFrame")
# Constructor
f.add_constructor_parameter("width", "200")
f.add_constructor_parameter("height", "200")
f.add_constructor_parameter("innerPadding", "5")
f.add_constructor_parameter("scrollable", "False")
f.add_constructor_parameter("style", "UIFrame.Raised")
# Functions
# Events
f.construct_sourcecode("LUIFrame")
# Construct a new frame
frame = LUIFrame(parent=f.get_widget_node())
layout = LUIVerticalLayout(parent=frame, spacing=5)
layout.add(LUILabel(text="This is some frame ..", color=(0.2, 0.6, 1.0, 1.0), font_size=20))
layout.add(LUILabel(text="It can contain arbitrary elements."))
layout.add(LUILabel(text="For example this button:"))
layout.add(LUIButton(text="Fancy button"))
# frame.fit_to_children()
f.set_actions({
"Resize to 300x160": lambda: frame.set_size(300, 160),
"Fit to children": lambda: frame.clear_size(),
})
run()

27
Demos/B_InputField.py Normal file
View File

@ -0,0 +1,27 @@
from DemoFramework import DemoFramework
from LUIInputField import LUIInputField
import random
f = DemoFramework()
f.prepare_demo("LUIInputField")
# Constructor
f.add_constructor_parameter("value", "u''")
f.add_constructor_parameter("placeholder", "u'Enter some text ..'")
f.add_property("value", "string")
f.add_event("changed")
# Construct source code
f.construct_sourcecode("LUIInputField")
# Create 2 new buttons, and store them in a vertical layout
field = LUIInputField(parent=f.get_widget_node())
f.set_actions({
"Set Random Text": lambda: field.set_value(u"Text: " + unicode(random.randint(100, 10000000))),
"Clear": lambda: field.clear(),
})
run()

36
Demos/B_Label.py Normal file
View File

@ -0,0 +1,36 @@
from DemoFramework import DemoFramework
from LUILabel import LUILabel
import random
f = DemoFramework()
f.prepare_demo("LUILabel")
# Constructor
f.add_constructor_parameter("text", "Label")
f.add_constructor_parameter("shadow", "True")
f.add_constructor_parameter("font_size", "14")
f.add_constructor_parameter("font", "'label'")
# Functions
f.add_public_function("get_text", [], "string")
f.add_public_function("set_text", [("text", "string")])
f.add_property("text", "string")
f.add_property("text_handle", "LUIText")
# Events
f.construct_sourcecode("LUILabel")
# Create a new label
label = LUILabel(parent=f.get_widget_node(), text="This is a fancy label")
f.set_actions({
"Set Random Text": lambda: label.set_text(str(random.randint(100, 10000))),
"Set Random Color": lambda: label.set_color(random.random(), random.random(), random.random(), 1)
})
run()

40
Demos/B_Progressbar.py Normal file
View File

@ -0,0 +1,40 @@
from DemoFramework import DemoFramework
from LUIProgressbar import LUIProgressbar
from LUISlider import LUISlider
from LUILabel import LUILabel
from LUIVerticalLayout import LUIVerticalLayout
import random
f = DemoFramework()
f.prepare_demo("LUIProgressbar")
# Constructor
f.add_constructor_parameter("show_label", "False")
# Functions
f.add_public_function("get_value", [], "float")
f.add_public_function("set_value", [("value", "float")])
f.add_property("value", "float")
# Events
f.construct_sourcecode("LUIProgressbar")
# Create the checkbox
layout = LUIVerticalLayout(parent=f.get_widget_node(), spacing=10)
LUILabel(parent=layout.cell(), text="This is a progressbar:", color=(1, 1, 1, 0.4))
bar = LUIProgressbar(parent=layout.cell(), width=200.0)
LUILabel(parent=layout.cell(), text="You can control it with this slider:", color=(1, 1, 1, 0.4))
slider = LUISlider(parent=layout.cell(), width=200.0, filled=True)
slider.bind("changed", lambda event: bar.set_value(slider.value * 100.0))
f.set_actions({
"Set to 30%": lambda: bar.set_value(30),
})
run()

49
Demos/B_Radiobox.py Normal file
View File

@ -0,0 +1,49 @@
from DemoFramework import DemoFramework
from LUIRadiobox import LUIRadiobox
from LUIRadioboxGroup import LUIRadioboxGroup
from LUIVerticalLayout import LUIVerticalLayout
import random
f = DemoFramework()
f.prepare_demo("LUIRadiobox")
# Constructor
f.add_constructor_parameter("group", "None")
f.add_constructor_parameter("value", "None")
f.add_constructor_parameter("label", "'Radiobox'")
# Functions
f.add_public_function("get_value", [], "object")
f.add_public_function("get_label", [], "LUILabel")
f.add_public_function("set_active", [], "void")
f.add_property("value", "object")
f.add_property("label", "LUILabel")
# Events
f.add_event("changed")
f.construct_sourcecode("LUIRadiobox")
# Create a group to connect the boxes
group = LUIRadioboxGroup()
# Create a layout for the boxes
grid = LUIVerticalLayout(parent=f.get_widget_node(), spacing=5)
# Create the boxes
boxes = []
for i in range(1, 4):
boxes.append(LUIRadiobox(group=group, value=i, label="Radiobox {0}".format(i), active=i==2))
grid.add(boxes[-1])
f.set_actions({
"Select Box 1": lambda: boxes[0].set_active(),
"Select Box 2": lambda: boxes[1].set_active(),
"Select Box 3": lambda: boxes[2].set_active(),
"Set Random Text": lambda: boxes[0].label.set_text("Text: " + str(random.randint(100, 10000))),
})
run()

53
Demos/B_Slider.py Normal file
View File

@ -0,0 +1,53 @@
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from DemoFramework import DemoFramework
from LUISlider import LUISlider
from LUILabel import LUILabel
from LUIVerticalLayout import LUIVerticalLayout
import random
f = DemoFramework()
f.prepare_demo("LUISlider")
# Constructor
f.add_constructor_parameter("filled", "False")
f.add_constructor_parameter("min_value", "0.0")
f.add_constructor_parameter("max_value", "0.0")
f.add_constructor_parameter("value", "None")
# Functions
f.add_public_function("get_value", [], "float")
f.add_public_function("set_value", [("value", "float")])
f.add_property("value", "float")
# Events
f.add_event("changed")
f.construct_sourcecode("LUISlider")
# Create the checkbox
layout = LUIVerticalLayout(parent=f.get_widget_node(), spacing=10)
LUILabel(parent=layout.cell(), text="This is a filled slider:", color=(1, 1, 1, 0.4))
slider = LUISlider(parent=layout.cell(), width=200.0)
LUILabel(parent=layout.cell(), text="This is a regular slider:", color=(1, 1, 1, 0.4))
slider_nofill = LUISlider(parent=layout.cell(), width=200.0, filled=False)
f.set_actions({
"Set to 30%": lambda: slider.set_value(0.3),
})
run()

251
Demos/DemoFramework.py Normal file
View File

@ -0,0 +1,251 @@
"""
This file contains some setup code for all the widget examples
"""
import sys
from pathlib import Path
lui_path = Path(__file__).parent.parent.absolute()
builtin_path = lui_path / "Builtin"
sys.path.insert(0, str(lui_path))
sys.path.insert(0, str(builtin_path))
import panda3d.core
import lui
import panda3d
panda3d.lui = lui
sys.modules["panda3d.lui"] = lui
from panda3d.core import *
from LUIRegion import LUIRegion
from LUIInputHandler import LUIInputHandler
from LUISprite import LUISprite
from LUIObject import LUIObject
from LUIVerticalLayout import LUIVerticalLayout
from LUILabel import LUILabel
from LUIFrame import LUIFrame
load_prc_file_data("", """
text-minfilter linear
text-magfilter linear
text-pixels-per-unit 32
sync-video #f
textures-power-2 none
notify-level-lui info
show-frame-rate-meter #t
win-size 780 630
window-title LUI Demo
win-fixed-size #f
""")
import direct.directbase.DirectStart
from LUISkin import LUIDefaultSkin
from LUICheckbox import LUICheckbox
from LUIFormattedLabel import LUIFormattedLabel
from LUISelectbox import LUISelectbox
from LUIButton import LUIButton
from Skins.Metro.LUIMetroSkin import LUIMetroSkin
class DemoFramework:
""" This is a small helper class to setup common stuff for the demos """
def __init__(self):
""" Constructs the demo framework """
if False:
self._skin = LUIMetroSkin()
base.win.set_clear_color(Vec4(1))
else:
self._skin = LUIDefaultSkin()
base.win.set_clear_color(Vec4(0.1, 0.0, 0.0, 1))
self._skin.load()
# Construct the LUIRegion
region = LUIRegion.make("LUI", base.win)
handler = LUIInputHandler()
base.mouseWatcher.attach_new_node(handler)
region.set_input_handler(handler)
self._root = region.root
self._constructor_params = []
def prepare_demo(self, demo_title=u"Some Demo"):
# Background
self._background = LUISprite(self._root, "res/DemoBackground.png")
# Make the background solid and recieve events
self._background.solid = True
# Logo
self._logo = LUISprite(self._root, "res/LUILogo.png")
self._logo.top_left = 15, 20
# Title
self._title_label = LUILabel(parent=self._root, text=demo_title, font_size=40,
font="header", pos=(120, 27))
self._subtitle_label = LUILabel(parent=self._root, text="Widget Demo", font_size=14,
font="default", pos=(121, 70), alpha=0.3)
# Right bar
self._right_bar = LUIVerticalLayout(parent=self._root)
self._left_bar = LUIVerticalLayout(parent=self._root)
self._right_bar.width = 350
self._right_bar.pos = (410, 120)
self._right_bar.spacing = 10
self._left_bar.width = 350
self._left_bar.pos=(20, 120)
self._left_bar.spacing = 10
# Public functions
self._public_functions = LUIFrame(width=340, style=LUIFrame.FS_sunken)
self._functions_label = LUILabel(text=U"Additional Public functions")
self._functions_layout = LUIVerticalLayout(parent=self._public_functions)
self._functions_layout.add(self._functions_label, 30)
# Events
self._events = LUIFrame(width=340, style=LUIFrame.FS_sunken)
self._events_label = LUILabel(text=U"Additional Events")
self._events_layout = LUIVerticalLayout(parent=self._events)
self._events_layout.add(self._events_label, 30)
# Actions
self._actions = LUIFrame(width=340, style=LUIFrame.FS_sunken)
self._actions_label = LUILabel(parent=self._actions, text=U"Demo-Actions")
self._actions_select = LUISelectbox(parent=self._actions, width=225, top=30)
self._actions_btn = LUIButton(parent=self._actions, right=0, top=30, text=u"Execute", template="ButtonGreen")
self._actions_btn.bind("click", self._exec_action)
# Properties
self._properties = LUIFrame(width=340, style=LUIFrame.FS_sunken)
self._properties_label = LUILabel(text=u"Additional Properties")
self._properties_layout = LUIVerticalLayout(parent=self._properties)
self._properties_layout.add(self._properties_label, 30)
self._right_bar.add(self._actions)
self._right_bar.add(self._public_functions)
self._right_bar.add(self._properties)
self._right_bar.add(self._events)
# Widget
self._widget_container = LUIFrame(width=360, height=250, style=LUIFrame.FS_sunken)
self._widget_label = LUILabel(parent=self._widget_container, text=u"Widget Demo")
self._left_bar.add(self._widget_container)
# Source Code
self._source_container = LUIFrame(width=360, height=190, style=LUIFrame.FS_sunken)
self._source_label = LUILabel(parent=self._source_container, text=u"Default Constructor")
self._copy_code_button = LUIButton(parent=self._source_container,
text=u"Copy to Clipboard", template="ButtonGreen", bottom_right=(0, 0))
self._source_content = LUIObject(self._source_container)
self._source_content.top = 40
self._left_bar.add(self._source_container)
self._widget_node = LUIObject(self._widget_container, x=0, y=40)
def _exec_action(self, event):
selected = self._actions_select.get_selected_option()
if selected is not None:
selected()
def set_actions(self, actions):
opts = []
for name, action in actions.items():
opts.append((action, name))
self._actions_select.set_options(opts)
def add_public_function(self, name, parameters=None, return_type="void"):
label = LUIFormattedLabel()
label.add(text=return_type + " ", color = (102/255.0, 217/255.0, 239/255.0))
label.add(text=name + " ", color = (166/255.0, 226/255.0, 46/255.0))
label.add(text="( ", color=(0.9,0.9,0.9))
if parameters is not None:
for index, (pname, ptype) in enumerate(parameters):
label.add(text=pname, color=(255/255.0, 151/255.0, 31/255.0))
label.add(text=" : ", color=(0.9,0.9,0.9))
label.add(text=ptype, color=(102/255.0, 217/255.0, 239/255.0))
if index < len(parameters) - 1:
label.add(text=",", color=(0.9,0.9,0.9))
label.add(text=" )", color=(0.9,0.9,0.9))
self._functions_layout.add(label)
self.update_layouts()
def add_constructor_parameter(self, name, default):
self._constructor_params.append((name, default))
self.update_layouts()
def add_event(self, event_name):
label = LUILabel(text=event_name)
label.color = (1,1,1,0.5)
self._events_layout.add(label)
self.update_layouts()
def add_property(self, property_name, property_type):
label = LUIFormattedLabel()
label.add(text=property_name, color=(255/255.0, 151/255.0, 31/255.0) )
label.add(" : ", color=(0.9,0.9,0.9) )
label.add(text=property_type + " ", color=(102/255.0, 217/255.0, 239/255.0) )
self._properties_layout.add(label)
self.update_layouts()
def update_layouts(self):
pass
def construct_sourcecode(self, classname):
self._source_content.remove_all_children()
label = LUIFormattedLabel(parent=self._source_content)
label.add(text="element ", color=(0.9,0.9,0.9))
label.add(text="= ", color=(249/255.0, 38/255.0, 114/255.0))
label.add(text=classname, color=(166/255.0, 226/255.0, 46/255.0))
label.add(text="(", color=(0.9,0.9,0.9))
for index, (pname, pvalue) in enumerate(self._constructor_params):
label.newline()
label.add(text=" " * 15)
label.add(text=pname, color=(255/255.0, 151/255.0, 31/255.0))
label.add(text=" = ")
label.add(text=pvalue, color=(153/255.0, 129/255.0, 255/255.0))
if index < len(self._constructor_params) - 1:
label.add(text=",")
label.add(text=")")
copy_text = "element = " + classname + "("
for index, (pname, pvalue) in enumerate(self._constructor_params):
copy_text += pname + "=" + pvalue
if index < len(self._constructor_params) - 1:
copy_text += ", "
copy_text += ")"
def copy_code(event):
# Copies the source code to clipboard
from Tkinter import Tk
r = Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append(copy_text)
r.destroy()
self._copy_code_button.bind("click", copy_code)
# self._source_content.fit_height_to_children()
# self._source_container.fit_height_to_children()
self._source_container.height += 40
def get_widget_node(self):
return self._widget_node

7
Demos/README.md Normal file
View File

@ -0,0 +1,7 @@
This directory contains various LUI demos. You should start with the demos
01 and 02.
After that, you can try out the demos starting with B_, showing builtin
LUI components.

0
Demos/__init__.py Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
Demos/res/LUILogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
Demos/res/LUILogoBlack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

22
LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
RenderPipeline
Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

70
README.md Normal file
View File

@ -0,0 +1,70 @@
[![Join the chat at https://gitter.im/tobspr/RenderPipeline](https://badges.gitter.im/tobspr/RenderPipeline.svg)](https://gitter.im/tobspr/RenderPipeline?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://travis-ci.org/tobspr/RenderPipeline.svg?branch=master)](https://travis-ci.org/tobspr/RenderPipeline)
<!-- # Render Pipeline -->
<img src="http://i.imgur.com/PO4OK4a.png" alt="Deferred Rendering Pipeline with Physically Based Shading" />
Deferred Realtime Rendering Pipeline with Physically Based Shading for the <a href="http://github.com/panda3d/panda3d">Panda3D Game Engine</a>.
### Core Features
- Physically Based Shading
- Deferred Rendering
- Advanced Post-Processing Effects and Framework
- Time of Day System
- Plugin System
## Screenshots
You can click on the images to enlarge them. Besides of that, you can find many more screenshots in my <a href="https://www.dropbox.com/sh/dq4wu3g9jwjqnht/AAABSOPnglDHZYsG5HXR-mhWa" target="_blank">dropbox folder</a>.
**Forest**
<img src="http://i.imgur.com/fD88ZMU.png" />
**Material demo**
<img src="http://i.imgur.com/M5YtvYR.png" />
**Screen space reflections**
<img src="http://i.imgur.com/oOwLXAK.png" />
**Car rendering**
<img src="http://i.imgur.com/hFD4qjV.png" alt="Car rendering" />
**Plugin and Time of Day editor:**
<img src="http://i.imgur.com/a8VpiHS.png" />
**Terrain and volumetric clouds**
<img src="http://i.imgur.com/zE0ywPl.png" />
See the <a target="_blank" href="https://github.com/tobspr/RenderPipeline/wiki/Features">Feature List</a>
for a list of features, and list of techniques I intend to implement.
You can find my todo list for the render pipeline here: <a href="https://trello.com/b/Li2JQi0q/render-pipeline" target="_blank">Render Pipeline Roadmap</a>.
### Getting Started / Wiki
You should checkout the wiki if you want to find out more about the pipeline:
<a target="_blank" href="https://github.com/tobspr/RenderPipeline/wiki">Render Pipeline WIKI</a>
There is also a page about getting started there: <a target="_blank" href="https://github.com/tobspr/RenderPipeline/wiki/Getting%20Started">Getting Started</a>
### Requirements
- OpenGL 4.3 capable GPU (and drivers)
- <a target="_blank" href="https://github.com/panda3d/panda3d">Panda3D</a> Development Build
- 1 GB Graphics Memory recommended *(Can run with less, depends on enabled plugins and resolution)*
**Notice**: It seems that the drivers for Intel HD Graphics on Linux are not
capable of all 4.3 features, so the pipeline is not able to run there!
If you want to use the C++ Modules, checkout <a href="https://github.com/tobspr/RenderPipeline/wiki/Building%20the%20CPP%20Modules" target="_blank">
Building the C++ Modules</a> to get a list of requirements for them.
### Reporting Bugs / Contributing
If you find bugs, or find information missing in the wiki, or want to contribute,
you can find me most of the time in the `#panda3d` channel on freenode.
If I shouldn't be there, feel free to contact me per E-Mail: `tobias.springer1@googlemail.com`

View File

@ -0,0 +1,5 @@
@echo off
cd res
ppython ../../../Misc/LUIAtlasGen.py
pause

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Some files were not shown because too many files have changed in this diff Show More