| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092 |
- """
- Base class for all DirectGui items. Handles composite widgets and
- command line argument parsing.
- Code overview:
- 1) Each widget defines a set of options (optiondefs) as a list of tuples
- of the form ``('name', defaultValue, handler)``.
- 'name' is the name of the option (used during construction of configure)
- handler can be: None, method, or INITOPT. If a method is specified, it
- will be called during widget construction (via initialiseoptions), if the
- Handler is specified as an INITOPT, this is an option that can only be set
- during widget construction.
- 2) :func:`~DirectGuiBase.defineoptions` is called. defineoption creates:
- self._constructorKeywords = { keyword: [value, useFlag] }
- A dictionary of the keyword options specified as part of the
- constructor keywords can be of the form 'component_option', where
- component is the name of a widget's component, a component group or a
- component alias.
- self._dynamicGroups
- A list of group names for which it is permissible to specify options
- before components of that group are created.
- If a widget is a derived class the order of execution would be::
- foo.optiondefs = {}
- foo.defineoptions()
- fooParent()
- fooParent.optiondefs = {}
- fooParent.defineoptions()
- 3) :func:`~DirectGuiBase.addoptions` is called. This combines options
- specified as keywords to the widget constructor (stored in
- self._constructorKeywords) with the default options (stored in optiondefs).
- Results are stored in
- ``self._optionInfo = { keyword: [default, current, handler] }``.
- If a keyword is of the form 'component_option' it is left in the
- self._constructorKeywords dictionary (for use by component constructors),
- otherwise it is 'used', and deleted from self._constructorKeywords.
- Notes:
- - constructor keywords override the defaults.
- - derived class default values override parent class defaults
- - derived class handler functions override parent class functions
- 4) Superclass initialization methods are called (resulting in nested calls
- to define options (see 2 above)
- 5) Widget components are created via calls to
- :func:`~DirectGuiBase.createcomponent`. User can specify aliases and groups
- for each component created.
- Aliases are alternate names for components, e.g. a widget may have a
- component with a name 'entryField', which itself may have a component
- named 'entry', you could add an alias 'entry' for the 'entryField_entry'
- These are stored in self.__componentAliases. If an alias is found,
- all keyword entries which use that alias are expanded to their full
- form (to avoid conversion later)
- Groups allow option specifications that apply to all members of the group.
- If a widget has components: 'text1', 'text2', and 'text3' which all belong
- to the 'text' group, they can be all configured with keywords of the form:
- 'text_keyword' (e.g. ``text_font='comic.rgb'``). A component's group
- is stored as the fourth element of its entry in self.__componentInfo.
- Note: the widget constructors have access to all remaining keywords in
- _constructorKeywords (those not transferred to _optionInfo by
- define/addoptions). If a component defines an alias that applies to
- one of the keywords, that keyword is replaced with a new keyword with
- the alias expanded.
- If a keyword (or substituted alias keyword) is used during creation of the
- component, it is deleted from self._constructorKeywords. If a group
- keyword applies to the component, that keyword is marked as used, but is
- not deleted from self._constructorKeywords, in case it applies to another
- component. If any constructor keywords remain at the end of component
- construction (and initialisation), an error is raised.
- 5) :func:`~DirectGuiBase.initialiseoptions` is called. This method calls any
- option handlers to respond to any keyword/default values, then checks to
- see if any keywords are left unused. If so, an error is raised.
- """
- __all__ = ['DirectGuiBase', 'DirectGuiWidget']
- from panda3d.core import *
- from direct.showbase import ShowBaseGlobal
- from direct.showbase.ShowBase import ShowBase
- from . import DirectGuiGlobals as DGG
- from .OnscreenText import *
- from .OnscreenGeom import *
- from .OnscreenImage import *
- from direct.directtools.DirectUtil import ROUND_TO
- from direct.showbase import DirectObject
- from direct.task import Task
- guiObjectCollector = PStatCollector("Client::GuiObjects")
- class DirectGuiBase(DirectObject.DirectObject):
- """Base class of all DirectGUI widgets."""
- def __init__(self):
- # Default id of all gui object, subclasses should override this
- self.guiId = 'guiObject'
- # List of all post initialization functions
- self.postInitialiseFuncList = []
- # To avoid doing things redundantly during initialisation
- self.fInit = 1
- # Mapping from each megawidget option to a list of information
- # about the option
- # - default value
- # - current value
- # - function to call when the option is initialised in the
- # call to initialiseoptions() in the constructor or
- # modified via configure(). If this is INITOPT, the
- # option is an initialisation option (an option that can
- # be set by the call to the constructor but can not be
- # used with configure).
- # This mapping is not initialised here, but in the call to
- # defineoptions() which precedes construction of this base class.
- #
- # self._optionInfo = {}
- # Mapping from each component name to a tuple of information
- # about the component.
- # - component widget instance
- # - configure function of widget instance
- # - the class of the widget (Frame, EntryField, etc)
- # - cget function of widget instance
- # - the name of the component group of this component, if any
- self.__componentInfo = {}
- # Mapping from alias names to the names of components or
- # sub-components.
- self.__componentAliases = {}
- # Contains information about the keywords provided to the
- # constructor. It is a mapping from the keyword to a tuple
- # containing:
- # - value of keyword
- # - a boolean indicating if the keyword has been used.
- # A keyword is used if, during the construction of a megawidget,
- # - it is defined in a call to defineoptions() or addoptions(), or
- # - it references, by name, a component of the megawidget, or
- # - it references, by group, at least one component
- # At the end of megawidget construction, a call is made to
- # initialiseoptions() which reports an error if there are
- # unused options given to the constructor.
- #
- # self._constructorKeywords = {}
- # List of dynamic component groups. If a group is included in
- # this list, then it not an error if a keyword argument for
- # the group is given to the constructor or to configure(), but
- # no components with this group have been created.
- # self._dynamicGroups = ()
- def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
- """ defineoptions(keywords, optionDefs, dynamicGroups = {}) """
- # Create options, providing the default value and the method
- # to call when the value is changed. If any option created by
- # base classes has the same name as one in <optionDefs>, the
- # base class's value and function will be overriden.
- # keywords is a dictionary of keyword/value pairs from the constructor
- # optionDefs is a dictionary of default options for the widget
- # dynamicGroups is a tuple of component groups for which you can
- # specify options even though no components of this group have
- # been created
- # This should be called before the constructor of the base
- # class, so that default values defined in the derived class
- # override those in the base class.
- if not hasattr(self, '_constructorKeywords'):
- tmp = {}
- for option, value in keywords.items():
- tmp[option] = [value, 0]
- self._constructorKeywords = tmp
- self._optionInfo = {}
- # Initialize dictionary of dynamic groups
- if not hasattr(self, '_dynamicGroups'):
- self._dynamicGroups = ()
- self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
- # Reconcile command line and default options
- self.addoptions(optionDefs, keywords)
- def addoptions(self, optionDefs, optionkeywords):
- """ addoptions(optionDefs) - add option def to option info """
- # Add additional options, providing the default value and the
- # method to call when the value is changed. See
- # "defineoptions" for more details
- # optimisations:
- optionInfo = self._optionInfo
- optionInfo_has_key = optionInfo.__contains__
- keywords = self._constructorKeywords
- keywords_has_key = keywords.__contains__
- FUNCTION = DGG._OPT_FUNCTION
- for name, default, function in optionDefs:
- if '_' not in name:
- default = optionkeywords.get(name, default)
- # The option will already exist if it has been defined
- # in a derived class. In this case, do not override the
- # default value of the option or the callback function
- # if it is not None.
- if not optionInfo_has_key(name):
- if keywords_has_key(name):
- # Overridden by keyword, use keyword value
- value = keywords[name][0]
- optionInfo[name] = [default, value, function]
- # Delete it from self._constructorKeywords
- del keywords[name]
- else:
- # Use optionDefs value
- optionInfo[name] = [default, default, function]
- elif optionInfo[name][FUNCTION] is None:
- # Only override function if not defined by derived class
- optionInfo[name][FUNCTION] = function
- else:
- # This option is of the form "component_option". If this is
- # not already defined in self._constructorKeywords add it.
- # This allows a derived class to override the default value
- # of an option of a component of a base class.
- if not keywords_has_key(name):
- keywords[name] = [default, 0]
- def initialiseoptions(self, myClass):
- """
- Call all initialisation functions to initialize widget
- options to default of keyword value
- """
- # This is to make sure this method class is only called by
- # the most specific class in the class hierarchy
- if self.__class__ is myClass:
- # Call the configuration callback function for every option.
- FUNCTION = DGG._OPT_FUNCTION
- self.fInit = 1
- for info in self._optionInfo.values():
- func = info[FUNCTION]
- if func is not None and func is not DGG.INITOPT:
- func()
- self.fInit = 0
- # Now check if anything is left over
- unusedOptions = []
- keywords = self._constructorKeywords
- for name in keywords:
- used = keywords[name][1]
- if not used:
- # This keyword argument has not been used. If it
- # does not refer to a dynamic group, mark it as
- # unused.
- index = name.find('_')
- if index < 0 or name[:index] not in self._dynamicGroups:
- unusedOptions.append(name)
- self._constructorKeywords = {}
- if len(unusedOptions) > 0:
- if len(unusedOptions) == 1:
- text = 'Unknown option "'
- else:
- text = 'Unknown options "'
- raise KeyError(text + ', '.join(unusedOptions) + \
- '" for ' + myClass.__name__)
- # Can now call post init func
- self.postInitialiseFunc()
- def postInitialiseFunc(self):
- for func in self.postInitialiseFuncList:
- func()
- def isinitoption(self, option):
- """
- Is this opition one that can only be specified at construction?
- """
- return self._optionInfo[option][DGG._OPT_FUNCTION] is DGG.INITOPT
- def options(self):
- """
- Print out a list of available widget options.
- Does not include subcomponent options.
- """
- options = []
- if hasattr(self, '_optionInfo'):
- for option, info in self._optionInfo.items():
- isinit = info[DGG._OPT_FUNCTION] is DGG.INITOPT
- default = info[DGG._OPT_DEFAULT]
- options.append((option, default, isinit))
- options.sort()
- return options
- def configure(self, option=None, **kw):
- """
- configure(option = None)
- Query or configure the megawidget options.
- """
- #
- # If not empty, *kw* is a dictionary giving new
- # values for some of the options of this gui item
- # For options defined for this widget, set
- # the value of the option to the new value and call the
- # configuration callback function, if any.
- #
- # If *option* is None, return all gui item configuration
- # options and settings. Options are returned as standard 3
- # element tuples
- #
- # If *option* is a string, return the 3 element tuple for the
- # given configuration option.
- # First, deal with the option queries.
- if len(kw) == 0:
- # This configure call is querying the values of one or all options.
- # Return 3-tuples:
- # (optionName, default, value)
- if option is None:
- rtn = {}
- for option, config in self._optionInfo.items():
- rtn[option] = (option,
- config[DGG._OPT_DEFAULT],
- config[DGG._OPT_VALUE])
- return rtn
- else:
- config = self._optionInfo[option]
- return (option, config[DGG._OPT_DEFAULT], config[DGG._OPT_VALUE])
- # optimizations:
- optionInfo = self._optionInfo
- optionInfo_has_key = optionInfo.__contains__
- componentInfo = self.__componentInfo
- componentInfo_has_key = componentInfo.__contains__
- componentAliases = self.__componentAliases
- componentAliases_has_key = componentAliases.__contains__
- VALUE = DGG._OPT_VALUE
- FUNCTION = DGG._OPT_FUNCTION
- # This will contain a list of options in *kw* which
- # are known to this gui item.
- directOptions = []
- # This will contain information about the options in
- # *kw* of the form <component>_<option>, where
- # <component> is a component of this megawidget. It is a
- # dictionary whose keys are the configure method of each
- # component and whose values are a dictionary of options and
- # values for the component.
- indirectOptions = {}
- indirectOptions_has_key = indirectOptions.__contains__
- for option, value in kw.items():
- if optionInfo_has_key(option):
- # This is one of the options of this gui item.
- # Check it is an initialisation option.
- if optionInfo[option][FUNCTION] is DGG.INITOPT:
- print('Cannot configure initialisation option "' \
- + option + '" for ' + self.__class__.__name__)
- break
- #raise KeyError, \
- # 'Cannot configure initialisation option "' \
- # + option + '" for ' + self.__class__.__name__
- optionInfo[option][VALUE] = value
- directOptions.append(option)
- else:
- index = option.find('_')
- if index >= 0:
- # This option may be of the form <component>_<option>.
- # e.g. if alias ('efEntry', 'entryField_entry')
- # and option = efEntry_width
- # component = efEntry, componentOption = width
- component = option[:index]
- componentOption = option[(index + 1):]
- # Expand component alias
- if componentAliases_has_key(component):
- # component = entryField, subcomponent = entry
- component, subComponent = componentAliases[component]
- if subComponent is not None:
- # componentOption becomes entry_width
- componentOption = subComponent + '_' \
- + componentOption
- # Expand option string to write on error
- # option = entryField_entry_width
- option = component + '_' + componentOption
- # Does this component exist
- if componentInfo_has_key(component):
- # Get the configure func for the named component
- # component = entryField
- componentConfigFuncs = [componentInfo[component][1]]
- else:
- # Check if this is a group name and configure all
- # components in the group.
- componentConfigFuncs = []
- # For each component
- for info in componentInfo.values():
- # Check if it is a member of this group
- if info[4] == component:
- # Yes, append its config func
- componentConfigFuncs.append(info[1])
- if len(componentConfigFuncs) == 0 and \
- component not in self._dynamicGroups:
- raise KeyError('Unknown option "' + option + \
- '" for ' + self.__class__.__name__)
- # Add the configure method(s) (may be more than
- # one if this is configuring a component group)
- # and option/value to dictionary.
- for componentConfigFunc in componentConfigFuncs:
- if not indirectOptions_has_key(componentConfigFunc):
- indirectOptions[componentConfigFunc] = {}
- # Create a dictionary of keyword/values keyed
- # on configuration function
- indirectOptions[componentConfigFunc][componentOption] \
- = value
- else:
- raise KeyError('Unknown option "' + option + \
- '" for ' + self.__class__.__name__)
- # Call the configure methods for any components.
- # Pass in the dictionary of keyword/values created above
- for func, options in indirectOptions.items():
- func(**options)
- # Call the configuration callback function for each option.
- for option in directOptions:
- info = optionInfo[option]
- func = info[DGG._OPT_FUNCTION]
- if func is not None:
- func()
- # Allow index style references
- def __setitem__(self, key, value):
- self.configure(**{key: value})
- def cget(self, option):
- """
- Get current configuration setting for this option
- """
- # Return the value of an option, for example myWidget['font'].
- if option in self._optionInfo:
- return self._optionInfo[option][DGG._OPT_VALUE]
- else:
- index = option.find('_')
- if index >= 0:
- component = option[:index]
- componentOption = option[(index + 1):]
- # Expand component alias
- if component in self.__componentAliases:
- component, subComponent = self.__componentAliases[
- component]
- if subComponent is not None:
- componentOption = subComponent + '_' + componentOption
- # Expand option string to write on error
- option = component + '_' + componentOption
- if component in self.__componentInfo:
- # Call cget on the component.
- componentCget = self.__componentInfo[component][3]
- return componentCget(componentOption)
- else:
- # If this is a group name, call cget for one of
- # the components in the group.
- for info in self.__componentInfo.values():
- if info[4] == component:
- componentCget = info[3]
- return componentCget(componentOption)
- # Option not found
- raise KeyError('Unknown option "' + option + \
- '" for ' + self.__class__.__name__)
- # Allow index style refererences
- __getitem__ = cget
- def createcomponent(self, componentName, componentAliases, componentGroup,
- widgetClass, *widgetArgs, **kw):
- """
- Create a component (during construction or later) for this widget.
- """
- # Check for invalid component name
- if '_' in componentName:
- raise ValueError('Component name "%s" must not contain "_"' % componentName)
- # Get construction keywords
- if hasattr(self, '_constructorKeywords'):
- keywords = self._constructorKeywords
- else:
- keywords = {}
- for alias, component in componentAliases:
- # Create aliases to the component and its sub-components.
- index = component.find('_')
- if index < 0:
- # Just a shorter name for one of this widget's components
- self.__componentAliases[alias] = (component, None)
- else:
- # An alias for a component of one of this widget's components
- mainComponent = component[:index]
- subComponent = component[(index + 1):]
- self.__componentAliases[alias] = (mainComponent, subComponent)
- # Remove aliases from the constructor keyword arguments by
- # replacing any keyword arguments that begin with *alias*
- # with corresponding keys beginning with *component*.
- alias = alias + '_'
- aliasLen = len(alias)
- for option in keywords.copy():
- if len(option) > aliasLen and option[:aliasLen] == alias:
- newkey = component + '_' + option[aliasLen:]
- keywords[newkey] = keywords[option]
- del keywords[option]
- # Find any keyword arguments for this component
- componentPrefix = componentName + '_'
- nameLen = len(componentPrefix)
- # First, walk through the option list looking for arguments
- # than refer to this component's group.
- for option in keywords:
- # Check if this keyword argument refers to the group
- # of this component. If so, add this to the options
- # to use when constructing the widget. Mark the
- # keyword argument as being used, but do not remove it
- # since it may be required when creating another
- # component.
- index = option.find('_')
- if index >= 0 and componentGroup == option[:index]:
- rest = option[(index + 1):]
- kw[rest] = keywords[option][0]
- keywords[option][1] = 1
- # Now that we've got the group arguments, walk through the
- # option list again and get out the arguments that refer to
- # this component specifically by name. These are more
- # specific than the group arguments, above; we walk through
- # the list afterwards so they will override.
- for option in keywords.copy():
- if len(option) > nameLen and option[:nameLen] == componentPrefix:
- # The keyword argument refers to this component, so add
- # this to the options to use when constructing the widget.
- kw[option[nameLen:]] = keywords[option][0]
- # And delete it from main construction keywords
- del keywords[option]
- # Return None if no widget class is specified
- if widgetClass is None:
- return None
- # Get arguments for widget constructor
- if len(widgetArgs) == 1 and type(widgetArgs[0]) == tuple:
- # Arguments to the constructor can be specified as either
- # multiple trailing arguments to createcomponent() or as a
- # single tuple argument.
- widgetArgs = widgetArgs[0]
- # Create the widget
- widget = widgetClass(*widgetArgs, **kw)
- componentClass = widget.__class__.__name__
- self.__componentInfo[componentName] = (widget, widget.configure,
- componentClass, widget.cget, componentGroup)
- return widget
- def component(self, name):
- # Return a component widget of the megawidget given the
- # component's name
- # This allows the user of a megawidget to access and configure
- # widget components directly.
- # Find the main component and any subcomponents
- index = name.find('_')
- if index < 0:
- component = name
- remainingComponents = None
- else:
- component = name[:index]
- remainingComponents = name[(index + 1):]
- # Expand component alias
- # Example entry which is an alias for entryField_entry
- if component in self.__componentAliases:
- # component = entryField, subComponent = entry
- component, subComponent = self.__componentAliases[component]
- if subComponent is not None:
- if remainingComponents is None:
- # remainingComponents = entry
- remainingComponents = subComponent
- else:
- remainingComponents = subComponent + '_' \
- + remainingComponents
- # Get the component from __componentInfo dictionary
- widget = self.__componentInfo[component][0]
- if remainingComponents is None:
- # Not looking for subcomponent
- return widget
- else:
- # Recursive call on subcomponent
- return widget.component(remainingComponents)
- def components(self):
- # Return a list of all components.
- names = list(self.__componentInfo.keys())
- names.sort()
- return names
- def hascomponent(self, component):
- return component in self.__componentInfo
- def destroycomponent(self, name):
- # Remove a megawidget component.
- # This command is for use by megawidget designers to destroy a
- # megawidget component.
- self.__componentInfo[name][0].destroy()
- del self.__componentInfo[name]
- def destroy(self):
- # Clean out any hooks
- self.ignoreAll()
- del self._optionInfo
- del self.__componentInfo
- del self.postInitialiseFuncList
- def bind(self, event, command, extraArgs = []):
- """
- Bind the command (which should expect one arg) to the specified
- event (such as ENTER, EXIT, B1PRESS, B1CLICK, etc.)
- See DirectGuiGlobals for possible events
- """
- # Need to tack on gui item specific id
- gEvent = event + self.guiId
- if ShowBaseGlobal.config.GetBool('debug-directgui-msgs', False):
- from direct.showbase.PythonUtil import StackTrace
- print(gEvent)
- print(StackTrace())
- self.accept(gEvent, command, extraArgs = extraArgs)
- def unbind(self, event):
- """
- Unbind the specified event
- """
- # Need to tack on gui item specific id
- gEvent = event + self.guiId
- self.ignore(gEvent)
- def toggleGuiGridSnap():
- DirectGuiWidget.snapToGrid = 1 - DirectGuiWidget.snapToGrid
- def setGuiGridSpacing(spacing):
- DirectGuiWidget.gridSpacing = spacing
- class DirectGuiWidget(DirectGuiBase, NodePath):
- # Toggle if you wish widget's to snap to grid when draggin
- snapToGrid = 0
- gridSpacing = 0.05
- # Determine the default initial state for inactive (or
- # unclickable) components. If we are in edit mode, these are
- # actually clickable by default.
- guiEdit = ShowBaseGlobal.config.GetBool('direct-gui-edit', False)
- if guiEdit:
- inactiveInitState = DGG.NORMAL
- else:
- inactiveInitState = DGG.DISABLED
- guiDict = {}
- def __init__(self, parent = None, **kw):
- # Direct gui widgets are node paths
- # Direct gui widgets have:
- # - stateNodePaths (to hold visible representation of widget)
- # State node paths can have:
- # - a frame of type (None, FLAT, RAISED, GROOVE, RIDGE)
- # - arbitrary geometry for each state
- # They inherit from DirectGuiWidget
- # - Can create components (with aliases and groups)
- # - Can bind to mouse events
- # They inherit from NodePath
- # - Can position/scale them
- optiondefs = (
- # Widget's constructor
- ('pgFunc', PGItem, None),
- ('numStates', 1, None),
- ('invertedFrames', (), None),
- ('sortOrder', 0, None),
- # Widget's initial state
- ('state', DGG.NORMAL, self.setState),
- # Widget's frame characteristics
- ('relief', DGG.FLAT, self.setRelief),
- ('borderWidth', (.1, .1), self.setBorderWidth),
- ('borderUvWidth', (.1, .1), self.setBorderUvWidth),
- ('frameSize', None, self.setFrameSize),
- ('frameColor', (.8, .8, .8, 1), self.setFrameColor),
- ('frameTexture', None, self.setFrameTexture),
- ('frameVisibleScale', (1, 1), self.setFrameVisibleScale),
- ('pad', (0, 0), self.resetFrameSize),
- # Override button id (beware! your name may not be unique!)
- ('guiId', None, DGG.INITOPT),
- # Initial pos/scale of the widget
- ('pos', None, DGG.INITOPT),
- ('hpr', None, DGG.INITOPT),
- ('scale', None, DGG.INITOPT),
- ('color', None, DGG.INITOPT),
- # Do events pass through this widget?
- ('suppressMouse', 1, DGG.INITOPT),
- ('suppressKeys', 0, DGG.INITOPT),
- ('enableEdit', 1, DGG.INITOPT),
- )
- # Merge keyword options with default options
- self.defineoptions(kw, optiondefs)
- # Initialize the base classes (after defining the options).
- DirectGuiBase.__init__(self)
- NodePath.__init__(self)
- # Create a button
- self.guiItem = self['pgFunc']('')
- # Override automatically generated guiId
- if self['guiId']:
- self.guiItem.setId(self['guiId'])
- self.guiId = self.guiItem.getId()
- if ShowBaseGlobal.__dev__:
- guiObjectCollector.addLevel(1)
- guiObjectCollector.flushLevel()
- # track gui items by guiId for tracking down leaks
- if ShowBaseGlobal.config.GetBool('track-gui-items', False):
- if not hasattr(ShowBase, 'guiItems'):
- ShowBase.guiItems = {}
- if self.guiId in ShowBase.guiItems:
- ShowBase.notify.warning('duplicate guiId: %s (%s stomping %s)' %
- (self.guiId, self,
- ShowBase.guiItems[self.guiId]))
- ShowBase.guiItems[self.guiId] = self
- # Attach button to parent and make that self
- if parent is None:
- parent = ShowBaseGlobal.aspect2d
- self.assign(parent.attachNewNode(self.guiItem, self['sortOrder']))
- # Update pose to initial values
- if self['pos']:
- self.setPos(self['pos'])
- if self['hpr']:
- self.setHpr(self['hpr'])
- if self['scale']:
- self.setScale(self['scale'])
- if self['color']:
- self.setColor(self['color'])
- # Initialize names
- # Putting the class name in helps with debugging.
- self.setName("%s-%s" % (self.__class__.__name__, self.guiId))
- # Create
- self.stateNodePath = []
- for i in range(self['numStates']):
- self.stateNodePath.append(NodePath(self.guiItem.getStateDef(i)))
- # Initialize frame style
- self.frameStyle = []
- for i in range(self['numStates']):
- self.frameStyle.append(PGFrameStyle())
- # For holding bounds info
- self.ll = Point3(0)
- self.ur = Point3(0)
- # Is drag and drop enabled?
- if self['enableEdit'] and self.guiEdit:
- self.enableEdit()
- # Set up event handling
- suppressFlags = 0
- if self['suppressMouse']:
- suppressFlags |= MouseWatcherRegion.SFMouseButton
- suppressFlags |= MouseWatcherRegion.SFMousePosition
- if self['suppressKeys']:
- suppressFlags |= MouseWatcherRegion.SFOtherButton
- self.guiItem.setSuppressFlags(suppressFlags)
- # Bind destroy hook
- self.guiDict[self.guiId] = self
- # self.bind(DGG.DESTROY, self.destroy)
- # Update frame when everything has been initialized
- self.postInitialiseFuncList.append(self.frameInitialiseFunc)
- # Call option initialization functions
- self.initialiseoptions(DirectGuiWidget)
- def frameInitialiseFunc(self):
- # Now allow changes to take effect
- self.updateFrameStyle()
- if not self['frameSize']:
- self.resetFrameSize()
- def enableEdit(self):
- self.bind(DGG.B2PRESS, self.editStart)
- self.bind(DGG.B2RELEASE, self.editStop)
- self.bind(DGG.PRINT, self.printConfig)
- # Can we move this to showbase
- # Certainly we don't need to do this for every button!
- #mb = base.mouseWatcherNode.getModifierButtons()
- #mb.addButton(KeyboardButton.control())
- #base.mouseWatcherNode.setModifierButtons(mb)
- def disableEdit(self):
- self.unbind(DGG.B2PRESS)
- self.unbind(DGG.B2RELEASE)
- self.unbind(DGG.PRINT)
- #mb = base.mouseWatcherNode.getModifierButtons()
- #mb.removeButton(KeyboardButton.control())
- #base.mouseWatcherNode.setModifierButtons(mb)
- def editStart(self, event):
- taskMgr.remove('guiEditTask')
- vWidget2render2d = self.getPos(render2d)
- vMouse2render2d = Point3(event.getMouse()[0], 0, event.getMouse()[1])
- editVec = Vec3(vWidget2render2d - vMouse2render2d)
- if base.mouseWatcherNode.getModifierButtons().isDown(
- KeyboardButton.control()):
- t = taskMgr.add(self.guiScaleTask, 'guiEditTask')
- t.refPos = vWidget2render2d
- t.editVecLen = editVec.length()
- t.initScale = self.getScale()
- else:
- t = taskMgr.add(self.guiDragTask, 'guiEditTask')
- t.editVec = editVec
- def guiScaleTask(self, state):
- mwn = base.mouseWatcherNode
- if mwn.hasMouse():
- vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
- newEditVecLen = Vec3(state.refPos - vMouse2render2d).length()
- self.setScale(state.initScale * (newEditVecLen/state.editVecLen))
- return Task.cont
- def guiDragTask(self, state):
- mwn = base.mouseWatcherNode
- if mwn.hasMouse():
- vMouse2render2d = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])
- newPos = vMouse2render2d + state.editVec
- self.setPos(render2d, newPos)
- if DirectGuiWidget.snapToGrid:
- newPos = self.getPos()
- newPos.set(
- ROUND_TO(newPos[0], DirectGuiWidget.gridSpacing),
- ROUND_TO(newPos[1], DirectGuiWidget.gridSpacing),
- ROUND_TO(newPos[2], DirectGuiWidget.gridSpacing))
- self.setPos(newPos)
- return Task.cont
- def editStop(self, event):
- taskMgr.remove('guiEditTask')
- def setState(self):
- if type(self['state']) == type(0):
- self.guiItem.setActive(self['state'])
- elif (self['state'] == DGG.NORMAL) or (self['state'] == 'normal'):
- self.guiItem.setActive(1)
- else:
- self.guiItem.setActive(0)
- def resetFrameSize(self):
- if not self.fInit:
- self.setFrameSize(fClearFrame = 1)
- def setFrameSize(self, fClearFrame = 0):
- # Use ready state to determine frame Type
- frameType = self.getFrameType()
- if self['frameSize']:
- # Use user specified bounds
- self.bounds = self['frameSize']
- #print "%s bounds = %s" % (self.getName(), self.bounds)
- bw = (0, 0)
- else:
- if fClearFrame and (frameType != PGFrameStyle.TNone):
- self.frameStyle[0].setType(PGFrameStyle.TNone)
- self.guiItem.setFrameStyle(0, self.frameStyle[0])
- # To force an update of the button
- self.guiItem.getStateDef(0)
- # Clear out frame before computing bounds
- self.getBounds()
- # Restore frame style if necessary
- if (frameType != PGFrameStyle.TNone):
- self.frameStyle[0].setType(frameType)
- self.guiItem.setFrameStyle(0, self.frameStyle[0])
- if ((frameType != PGFrameStyle.TNone) and
- (frameType != PGFrameStyle.TFlat)):
- bw = self['borderWidth']
- else:
- bw = (0, 0)
- # Set frame to new dimensions
- self.guiItem.setFrame(
- self.bounds[0] - bw[0],
- self.bounds[1] + bw[0],
- self.bounds[2] - bw[1],
- self.bounds[3] + bw[1])
- def getBounds(self, state = 0):
- self.stateNodePath[state].calcTightBounds(self.ll, self.ur)
- # Scale bounds to give a pad around graphics
- vec_right = Vec3.right()
- vec_up = Vec3.up()
- left = (vec_right[0] * self.ll[0]
- + vec_right[1] * self.ll[1]
- + vec_right[2] * self.ll[2])
- right = (vec_right[0] * self.ur[0]
- + vec_right[1] * self.ur[1]
- + vec_right[2] * self.ur[2])
- bottom = (vec_up[0] * self.ll[0]
- + vec_up[1] * self.ll[1]
- + vec_up[2] * self.ll[2])
- top = (vec_up[0] * self.ur[0]
- + vec_up[1] * self.ur[1]
- + vec_up[2] * self.ur[2])
- self.ll = Point3(left, 0.0, bottom)
- self.ur = Point3(right, 0.0, top)
- self.bounds = [self.ll[0] - self['pad'][0],
- self.ur[0] + self['pad'][0],
- self.ll[2] - self['pad'][1],
- self.ur[2] + self['pad'][1]]
- return self.bounds
- def getWidth(self):
- return self.bounds[1] - self.bounds[0]
- def getHeight(self):
- return self.bounds[3] - self.bounds[2]
- def getCenter(self):
- x = self.bounds[0] + (self.bounds[1] - self.bounds[0])/2.0
- y = self.bounds[2] + (self.bounds[3] - self.bounds[2])/2.0
- return (x, y)
- def getFrameType(self, state = 0):
- return self.frameStyle[state].getType()
- def updateFrameStyle(self):
- if not self.fInit:
- for i in range(self['numStates']):
- self.guiItem.setFrameStyle(i, self.frameStyle[i])
- def setRelief(self, fSetStyle = 1):
- relief = self['relief']
- # Convert None, and string arguments
- if relief == None:
- relief = PGFrameStyle.TNone
- elif isinstance(relief, str):
- # Convert string to frame style int
- relief = DGG.FrameStyleDict[relief]
- # Set style
- if relief == DGG.RAISED:
- for i in range(self['numStates']):
- if i in self['invertedFrames']:
- self.frameStyle[1].setType(DGG.SUNKEN)
- else:
- self.frameStyle[i].setType(DGG.RAISED)
- elif relief == DGG.SUNKEN:
- for i in range(self['numStates']):
- if i in self['invertedFrames']:
- self.frameStyle[1].setType(DGG.RAISED)
- else:
- self.frameStyle[i].setType(DGG.SUNKEN)
- else:
- for i in range(self['numStates']):
- self.frameStyle[i].setType(relief)
- # Apply styles
- self.updateFrameStyle()
- def setFrameColor(self):
- # this might be a single color or a list of colors
- colors = self['frameColor']
- if type(colors[0]) == int or \
- type(colors[0]) == float:
- colors = (colors,)
- for i in range(self['numStates']):
- if i >= len(colors):
- color = colors[-1]
- else:
- color = colors[i]
- self.frameStyle[i].setColor(color[0], color[1], color[2], color[3])
- self.updateFrameStyle()
- def setFrameTexture(self):
- # this might be a single texture or a list of textures
- textures = self['frameTexture']
- if textures == None or \
- isinstance(textures, Texture) or \
- isinstance(textures, str):
- textures = (textures,) * self['numStates']
- for i in range(self['numStates']):
- if i >= len(textures):
- texture = textures[-1]
- else:
- texture = textures[i]
- if isinstance(texture, str):
- texture = loader.loadTexture(texture)
- if texture:
- self.frameStyle[i].setTexture(texture)
- else:
- self.frameStyle[i].clearTexture()
- self.updateFrameStyle()
- def setFrameVisibleScale(self):
- scale = self['frameVisibleScale']
- for i in range(self['numStates']):
- self.frameStyle[i].setVisibleScale(scale[0], scale[1])
- self.updateFrameStyle()
- def setBorderWidth(self):
- width = self['borderWidth']
- for i in range(self['numStates']):
- self.frameStyle[i].setWidth(width[0], width[1])
- self.updateFrameStyle()
- def setBorderUvWidth(self):
- uvWidth = self['borderUvWidth']
- for i in range(self['numStates']):
- self.frameStyle[i].setUvWidth(uvWidth[0], uvWidth[1])
- self.updateFrameStyle()
- def destroy(self):
- if hasattr(self, "frameStyle"):
- if ShowBaseGlobal.__dev__:
- guiObjectCollector.subLevel(1)
- guiObjectCollector.flushLevel()
- if hasattr(ShowBase, 'guiItems'):
- ShowBase.guiItems.pop(self.guiId, None)
- # Destroy children
- for child in self.getChildren():
- childGui = self.guiDict.get(child.getName())
- if childGui:
- childGui.destroy()
- else:
- # RAU since we added the class to the name, try
- # it with the original name
- parts = child.getName().split('-')
- simpleChildGui = self.guiDict.get(parts[-1])
- if simpleChildGui:
- simpleChildGui.destroy()
- # messenger.send(DESTROY + child.getName())
- del self.guiDict[self.guiId]
- del self.frameStyle
- # Get rid of node path
- self.removeNode()
- for nodePath in self.stateNodePath:
- nodePath.removeNode()
- del self.stateNodePath
- del self.guiItem
- # Call superclass destruction method (clears out hooks)
- DirectGuiBase.destroy(self)
- def printConfig(self, indent = 0):
- space = ' ' * indent
- print('%s%s - %s' % (space, self.guiId, self.__class__.__name__))
- print('%sPos: %s' % (space, tuple(self.getPos())))
- print('%sScale: %s' % (space, tuple(self.getScale())))
- # Print out children info
- for child in self.getChildren():
- messenger.send(DGG.PRINT + child.getName(), [indent + 2])
- def copyOptions(self, other):
- """
- Copy other's options into our self so we look and feel like other
- """
- for key, value in other._optionInfo.items():
- self[key] = value[1]
- def taskName(self, idString):
- return (idString + "-" + str(self.guiId))
- def uniqueName(self, idString):
- return (idString + "-" + str(self.guiId))
- def setProp(self, propString, value):
- """
- Allows you to set a property like frame['text'] = 'Joe' in
- a function instead of an assignment.
- This is useful for setting properties inside function intervals
- where must input a function and extraArgs, not an assignment.
- """
- self[propString] = value
|