init
4
.flake8
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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")
|
||||
47
Builtin/LUIFormattedLabel.py
Normal 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
@ -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)
|
||||
20
Builtin/LUIHorizontalLayout.py
Normal 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)
|
||||
41
Builtin/LUIInitialState.py
Normal 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
@ -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)
|
||||
8
Builtin/LUIInputHandler.py
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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")
|
||||
41
Builtin/LUIRadioboxGroup.py
Normal 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
@ -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
@ -0,0 +1,8 @@
|
||||
"""
|
||||
|
||||
This is a wrapper file. It contains no actual implementation
|
||||
|
||||
"""
|
||||
|
||||
from panda3d.lui import LUIRoot as __LUIRoot
|
||||
LUIRoot = __LUIRoot
|
||||
155
Builtin/LUIScrollableRegion.py
Normal 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
@ -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
@ -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
@ -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
@ -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)
|
||||
30
Builtin/LUISpriteButton.py
Normal 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
@ -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")
|
||||
20
Builtin/LUIVerticalLayout.py
Normal 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
1
Demos/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
Test.py
|
||||
56
Demos/01_MinimalExample.py
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
BIN
Demos/res/DemoBackground.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
Demos/res/LUILogo.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Demos/res/LUILogoBlack.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
22
LICENSE.txt
Normal 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
@ -0,0 +1,70 @@
|
||||
[](https://gitter.im/tobspr/RenderPipeline?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](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`
|
||||
|
||||
5
Skins/Default/GenerateAtlas.bat
Normal file
@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
|
||||
cd res
|
||||
ppython ../../../Misc/LUIAtlasGen.py
|
||||
pause
|
||||
0
Skins/Default/__init__.py
Normal file
BIN
Skins/Default/font/SourceSansPro-Black.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-BlackIt.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-Bold.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-BoldIt.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-ExtraLight.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-ExtraLightIt.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-It.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-Light.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-LightIt.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-Regular.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-Semibold.ttf
Normal file
BIN
Skins/Default/font/SourceSansPro-SemiboldIt.ttf
Normal file
BIN
Skins/Default/res/ButtonDefault.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/ButtonDefaultFocus.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/ButtonDefaultFocus_Left.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Skins/Default/res/ButtonDefaultFocus_Right.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Skins/Default/res/ButtonDefault_Left.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Skins/Default/res/ButtonDefault_Right.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Skins/Default/res/ButtonGreen.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/ButtonGreenFocus.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/ButtonGreenFocus_Left.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Skins/Default/res/ButtonGreenFocus_Right.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Skins/Default/res/ButtonGreen_Left.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Skins/Default/res/ButtonGreen_Right.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Skins/Default/res/Checkbox_Checked.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Skins/Default/res/Checkbox_CheckedHover.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Skins/Default/res/Checkbox_Default.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/Checkbox_DefaultHover.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/ColorpickerActiveColorOverlay.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Skins/Default/res/ColorpickerFieldHandle.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
Skins/Default/res/ColorpickerFieldOverlay.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
Skins/Default/res/ColorpickerHueHandle.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Skins/Default/res/ColorpickerHueSlider.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
Skins/Default/res/ColorpickerPreviewBg.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Skins/Default/res/ColorpickerPreviewOverlay.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
Skins/Default/res/Frame_BL.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Skins/Default/res/Frame_BR.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Skins/Default/res/Frame_Bottom.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Skins/Default/res/Frame_Left.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Skins/Default/res/Frame_Mid.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
Skins/Default/res/Frame_Right.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Skins/Default/res/Frame_TL.png
Normal file
|
After Width: | Height: | Size: 732 B |
BIN
Skins/Default/res/Frame_TR.png
Normal file
|
After Width: | Height: | Size: 727 B |
BIN
Skins/Default/res/Frame_Top.png
Normal file
|
After Width: | Height: | Size: 874 B |
BIN
Skins/Default/res/HorizontalListDivider.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Skins/Default/res/InputField.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |