// Filename: pgItem.cxx // Created by: drose (13Mar02) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "pgItem.h" #include "pgMouseWatcherParameter.h" #include "pgCullTraverser.h" #include "config_pgui.h" #include "pandaNode.h" #include "sceneGraphReducer.h" #include "throw_event.h" #include "string_utils.h" #include "nodePath.h" #include "cullTraverser.h" #include "cullTraverserData.h" #include "cullBinManager.h" #include "dcast.h" #ifdef HAVE_AUDIO #include "audioSound.h" #endif TypeHandle PGItem::_type_handle; PT(TextNode) PGItem::_text_node; PGItem *PGItem::_focus_item = (PGItem *)NULL; PGItem::BackgroundFocus PGItem::_background_focus; //////////////////////////////////////////////////////////////////// // Function: PGItem::Constructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// PGItem:: PGItem(const string &name) : PandaNode(name) { _has_frame = false; _frame.set(0, 0, 0, 0); _region = new PGMouseWatcherRegion(this); _state = 0; _flags = 0; } //////////////////////////////////////////////////////////////////// // Function: PGItem::Destructor // Access: Public, Virtual // Description: //////////////////////////////////////////////////////////////////// PGItem:: ~PGItem() { nassertv(_region->_item == this); _region->_item = (PGItem *)NULL; set_background_focus(false); if (_focus_item == this) { _focus_item = (PGItem *)NULL; } } //////////////////////////////////////////////////////////////////// // Function: PGItem::Copy Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// PGItem:: PGItem(const PGItem ©) : PandaNode(copy), _has_frame(copy._has_frame), _frame(copy._frame), _state(copy._state), _flags(copy._flags) { _region = new PGMouseWatcherRegion(this); } //////////////////////////////////////////////////////////////////// // Function: PGItem::make_copy // Access: Protected, Virtual // Description: Returns a newly-allocated Node that is a shallow copy // of this one. It will be a different Node pointer, // but its internal data may or may not be shared with // that of the original Node. //////////////////////////////////////////////////////////////////// PandaNode *PGItem:: make_copy() const { return new PGItem(*this); } //////////////////////////////////////////////////////////////////// // Function: PGItem::xform // Access: Protected, Virtual // Description: Transforms the contents of this node by the indicated // matrix, if it means anything to do so. For most // kinds of nodes, this does nothing. //////////////////////////////////////////////////////////////////// void PGItem:: xform(const LMatrix4f &mat) { // Transform the frame. LPoint3f ll(_frame[0], 0.0f, _frame[2]); LPoint3f ur(_frame[1], 0.0f, _frame[3]); ll = ll * mat; ur = ur * mat; _frame.set(ll[0], ur[0], ll[2], ur[2]); // Transform the individual states and their frame styles. StateDefs::iterator di; for (di = _state_defs.begin(); di != _state_defs.end(); ++di) { NodePath &root = (*di)._root; // Apply the matrix to the previous transform. root.set_transform(root.get_transform()->compose(TransformState::make_mat(mat))); // Now flatten the transform into the subgraph. SceneGraphReducer gr; gr.apply_attribs(root.node()); // Transform the frame style too. if ((*di)._frame_style.xform(mat)) { (*di)._frame_stale = true; } } mark_bound_stale(); } //////////////////////////////////////////////////////////////////// // Function: PGItem::has_cull_callback // Access: Protected, Virtual // Description: Should be overridden by derived classes to return // true if cull_callback() has been defined. Otherwise, // returns false to indicate cull_callback() does not // need to be called for this node during the cull // traversal. //////////////////////////////////////////////////////////////////// bool PGItem:: has_cull_callback() const { return true; } //////////////////////////////////////////////////////////////////// // Function: PGItem::cull_callback // Access: Protected, Virtual // Description: If has_cull_callback() returns true, this function // will be called during the cull traversal to perform // any additional operations that should be performed at // cull time. This may include additional manipulation // of render state or additional visible/invisible // decisions, or any other arbitrary operation. // // By the time this function is called, the node has // already passed the bounding-volume test for the // viewing frustum, and the node's transform and state // have already been applied to the indicated // CullTraverserData object. // // The return value is true if this node should be // visible, or false if it should be culled. //////////////////////////////////////////////////////////////////// bool PGItem:: cull_callback(CullTraverser *trav, CullTraverserData &data) { if (has_frame() && get_active()) { // The item has a frame, so we want to generate a region for it // and update the MouseWatcher. // We can only do this if our traverser is a PGCullTraverser // (which will be the case if this node was parented somewhere // under a PGTop node). if (trav->is_exact_type(PGCullTraverser::get_class_type())) { PGCullTraverser *pg_trav; DCAST_INTO_R(pg_trav, trav, true); const LMatrix4f &transform = data._net_transform->get_mat(); // Consider the cull bin this object is in. Since the binning // affects the render order, we want bins that render later to // get higher sort values. int bin_index = data._state->get_bin_index(); int sort; CullBinManager *bin_manager = CullBinManager::get_global_ptr(); CullBinManager::BinType bin_type = bin_manager->get_bin_type(bin_index); if (bin_type == CullBinManager::BT_fixed) { // If the bin is a "fixed" type bin, our local sort is based // on the fixed order. sort = data._state->get_draw_order(); } else if (bin_type == CullBinManager::BT_unsorted) { // If the bin is an "unsorted" type bin, we base the local // sort on the scene graph order. sort = pg_trav->_sort_index; pg_trav->_sort_index++; } else { // Otherwise, the local sort is irrelevant. sort = 0; } // Now what order does this bin sort relative to the other bins? // This becomes the high-order part of the final sort count. int bin_sort = bin_manager->get_bin_sort(data._state->get_bin_index()); // Combine the two sorts into a single int. This assumes we // only need 16 bits for each sort number, possibly an erroneous // assumption. We should really provide two separate sort // values, both ints, in the MouseWatcherRegion; but in the // interest of expediency we work within the existing interface // which only provides one. sort = (bin_sort << 16) | ((sort + 0x8000) & 0xffff); activate_region(transform, sort); pg_trav->_top->add_region(get_region()); } } if (has_state_def(get_state())) { // This item has a current state definition that we should use // to render the item. NodePath &root = get_state_def(get_state()); CullTraverserData next_data(data, root.node()); trav->traverse(next_data); } // Now continue to render everything else below this node. return true; } //////////////////////////////////////////////////////////////////// // Function: PGItem::recompute_internal_bound // Access: Protected, Virtual // Description: Called when needed to recompute the node's // _internal_bound object. Nodes that contain anything // of substance should redefine this to do the right // thing. //////////////////////////////////////////////////////////////////// BoundingVolume *PGItem:: recompute_internal_bound() { // First, get ourselves a fresh, empty bounding volume. BoundingVolume *bound = PandaNode::recompute_internal_bound(); nassertr(bound != (BoundingVolume *)NULL, bound); // Now actually compute the bounding volume by putting it around all // of our states' bounding volumes. pvector child_volumes; // We walk through the list of state defs indirectly, calling // get_state_def() on each one, to ensure that the frames are // updated correctly before we measure their bounding volumes. for (int i = 0; i < (int)_state_defs.size(); i++) { NodePath &root = get_state_def(i); if (!root.is_empty()) { child_volumes.push_back(&root.node()->get_bound()); } } const BoundingVolume **child_begin = &child_volumes[0]; const BoundingVolume **child_end = child_begin + child_volumes.size(); bound->around(child_begin, child_end); return bound; } //////////////////////////////////////////////////////////////////// // Function: PGItem::activate_region // Access: Public // Description: Applies the indicated scene graph transform and order // as determined by the traversal from PGTop. //////////////////////////////////////////////////////////////////// void PGItem:: activate_region(const LMatrix4f &transform, int sort) { // Transform all four vertices, and get the new bounding box. This // way the region works (mostly) even if has been rotated. LPoint3f ll(_frame[0], 0.0f, _frame[2]); LPoint3f lr(_frame[1], 0.0f, _frame[2]); LPoint3f ul(_frame[0], 0.0f, _frame[3]); LPoint3f ur(_frame[1], 0.0f, _frame[3]); ll = ll * transform; lr = lr * transform; ul = ul * transform; ur = ur * transform; _region->set_frame(min(min(ll[0], lr[0]), min(ul[0], ur[0])), max(max(ll[0], lr[0]), max(ul[0], ur[0])), min(min(ll[2], lr[2]), min(ul[2], ur[2])), max(max(ll[2], lr[2]), max(ul[2], ur[2]))); _region->set_sort(sort); _region->set_active(true); // calculate the inverse of this transform, which is needed to // go back to the frame space. _frame_inv_xform.invert_from(transform); } //////////////////////////////////////////////////////////////////// // Function: PGItem::enter // Access: Public, Virtual // Description: This is a callback hook function, called whenever the // mouse enters the region. The mouse is only // considered to be "entered" in one region at a time; // in the case of nested regions, it exits the outer // region before entering the inner one. //////////////////////////////////////////////////////////////////// void PGItem:: enter(const MouseWatcherParameter ¶m) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_enter_event(); play_sound(event); throw_event(event, EventParameter(ep)); //pgui_cat.info() << get_name() << "::enter()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::exit // Access: Public, Virtual // Description: This is a callback hook function, called whenever the // mouse exits the region. The mouse is only considered // to be "entered" in one region at a time; in the case // of nested regions, it exits the outer region before // entering the inner one. //////////////////////////////////////////////////////////////////// void PGItem:: exit(const MouseWatcherParameter ¶m) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_exit_event(); play_sound(event); throw_event(event, EventParameter(ep)); //pgui_cat.info() << get_name() << "::exit()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::within // Access: Public, Virtual // Description: This is a callback hook function, called whenever the // mouse moves within the boundaries of the region, even // if it is also within the boundaries of a nested // region. This is different from "enter", which is // only called whenever the mouse is within only that // region. //////////////////////////////////////////////////////////////////// void PGItem:: within(const MouseWatcherParameter ¶m) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_within_event(); play_sound(event); throw_event(event, EventParameter(ep)); //pgui_cat.info() << get_name() << "::within()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::without // Access: Public, Virtual // Description: This is a callback hook function, called whenever the // mouse moves completely outside the boundaries of the // region. See within(). //////////////////////////////////////////////////////////////////// void PGItem:: without(const MouseWatcherParameter ¶m) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_without_event(); play_sound(event); throw_event(event, EventParameter(ep)); //pgui_cat.info() << get_name() << "::without()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::focus_in // Access: Public, Virtual // Description: This is a callback hook function, called whenever the // widget gets the keyboard focus. //////////////////////////////////////////////////////////////////// void PGItem:: focus_in() { string event = get_focus_in_event(); play_sound(event); throw_event(event); //pgui_cat.info() << get_name() << "::focus_in()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::focus_out // Access: Public, Virtual // Description: This is a callback hook function, called whenever the // widget loses the keyboard focus. //////////////////////////////////////////////////////////////////// void PGItem:: focus_out() { string event = get_focus_out_event(); play_sound(event); throw_event(event); //pgui_cat.info() << get_name() << "::focus_out()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::press // Access: Public, Virtual // Description: This is a callback hook function, called whenever a // mouse or keyboard button is depressed while the mouse // is within the region. //////////////////////////////////////////////////////////////////// void PGItem:: press(const MouseWatcherParameter ¶m, bool background) { if (!background) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_press_event(param.get_button()); play_sound(event); throw_event(event, EventParameter(ep)); } //pgui_cat.info() << get_name() << "::press()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::release // Access: Public, Virtual // Description: This is a callback hook function, called whenever a // mouse or keyboard button previously depressed with // press() is released. //////////////////////////////////////////////////////////////////// void PGItem:: release(const MouseWatcherParameter ¶m, bool background) { if (!background) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_release_event(param.get_button()); play_sound(event); throw_event(event, EventParameter(ep)); } //pgui_cat.info() << get_name() << "::release()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::keystroke // Access: Public, Virtual // Description: This is a callback hook function, called whenever // the user presses a key. //////////////////////////////////////////////////////////////////// void PGItem:: keystroke(const MouseWatcherParameter ¶m, bool background) { if (!background) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_keystroke_event(); play_sound(event); throw_event(event, EventParameter(ep)); } } //////////////////////////////////////////////////////////////////// // Function: PGItem::candidate // Access: Public, Virtual // Description: This is a callback hook function, called whenever // the user highlights an option in the IME window. //////////////////////////////////////////////////////////////////// void PGItem:: candidate(const MouseWatcherParameter ¶m, bool background) { // We don't throw sound events for candidate selections for now. } //////////////////////////////////////////////////////////////////// // Function: PGItem::move // Access: Public, Virtual // Description: This is a callback hook function, called whenever a // mouse is moved while within the region. //////////////////////////////////////////////////////////////////// void PGItem:: move(const MouseWatcherParameter ¶m) { PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); string event = get_press_event(param.get_button()); play_sound(event); throw_event(event, EventParameter(ep)); //pgui_cat.info() << get_name() << "::move()" << endl; } //////////////////////////////////////////////////////////////////// // Function: PGItem::background_press // Access: Public, Static // Description: Calls press() on all the PGItems with background // focus. //////////////////////////////////////////////////////////////////// void PGItem:: background_press(const MouseWatcherParameter ¶m) { BackgroundFocus::const_iterator fi; for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { PGItem *item = *fi; if (!item->get_focus()) { item->press(param, true); } } } //////////////////////////////////////////////////////////////////// // Function: PGItem::background_release // Access: Public, Static // Description: Calls release() on all the PGItems with background // focus. //////////////////////////////////////////////////////////////////// void PGItem:: background_release(const MouseWatcherParameter ¶m) { BackgroundFocus::const_iterator fi; for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { PGItem *item = *fi; if (!item->get_focus()) { item->release(param, true); } } } //////////////////////////////////////////////////////////////////// // Function: PGItem::background_keystroke // Access: Public, Static // Description: Calls keystroke() on all the PGItems with background // focus. //////////////////////////////////////////////////////////////////// void PGItem:: background_keystroke(const MouseWatcherParameter ¶m) { BackgroundFocus::const_iterator fi; for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { PGItem *item = *fi; if (!item->get_focus()) { item->keystroke(param, true); } } } //////////////////////////////////////////////////////////////////// // Function: PGItem::background_candidate // Access: Public, Static // Description: Calls candidate() on all the PGItems with background // focus. //////////////////////////////////////////////////////////////////// void PGItem:: background_candidate(const MouseWatcherParameter ¶m) { BackgroundFocus::const_iterator fi; for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { PGItem *item = *fi; if (!item->get_focus()) { item->candidate(param, true); } } } //////////////////////////////////////////////////////////////////// // Function: PGItem::set_active // Access: Published, Virtual // Description: Sets whether the PGItem is active for mouse watching. // This is not necessarily related to the // active/inactive appearance of the item, which is // controlled by set_state(), but it does affect whether // it responds to mouse events. //////////////////////////////////////////////////////////////////// void PGItem:: set_active(bool active) { if (active) { _flags |= F_active; } else { _flags &= ~F_active; // Deactivating the item automatically defocuses it too. if (get_focus()) { set_focus(false); } } } //////////////////////////////////////////////////////////////////// // Function: PGItem::set_focus // Access: Published, Virtual // Description: Sets whether the PGItem currently has keyboard focus. // This simply means that the item may respond to // keyboard events as well as to mouse events; precisely // what this means is up to the individual item. // // Only one PGItem in the world is allowed to have focus // at any given time. Setting the focus on any other // item automatically disables the focus from the // previous item. //////////////////////////////////////////////////////////////////// void PGItem:: set_focus(bool focus) { if (focus) { if (!get_active()) { // Cannot set focus on an inactive item. return; } // Set the keyboard focus to this item. if (_focus_item != this) { if (_focus_item != (PGItem *)NULL) { // Clear the focus from whatever item currently has it. _focus_item->set_focus(false); } _focus_item = this; } if (!get_focus()) { focus_in(); _flags |= F_focus; } } else { if (_focus_item == this) { // Remove this item from the focus. _focus_item = (PGItem *)NULL; } if (get_focus()) { focus_out(); _flags &= ~F_focus; } } _region->set_keyboard(focus); } //////////////////////////////////////////////////////////////////// // Function: PGItem::set_background_focus // Access: Published // Description: Sets the background_focus flag for this item. When // background_focus is enabled, the item will receive // keypress events even if it is not in focus; in fact, // even if it is not onscreen. Unlike normal focus, // many items may have background_focus simultaneously. //////////////////////////////////////////////////////////////////// void PGItem:: set_background_focus(bool focus) { if (focus != get_background_focus()) { if (focus) { // Activate background focus. _flags |= F_background_focus; bool inserted = _background_focus.insert(this).second; nassertv(inserted); } else { // Deactivate background focus. _flags &= ~F_background_focus; size_t num_erased = _background_focus.erase(this); nassertv(num_erased == 1); } } } //////////////////////////////////////////////////////////////////// // Function: PGItem::get_num_state_defs // Access: Published // Description: Returns one more than the highest-numbered state def // that was ever assigned to the PGItem. The complete // set of state defs assigned may then be retrieved by // indexing from 0 to (get_num_state_defs() - 1). // // This is only an upper limit on the actual number of // state defs, since there may be holes in the list. //////////////////////////////////////////////////////////////////// int PGItem:: get_num_state_defs() const { return _state_defs.size(); } //////////////////////////////////////////////////////////////////// // Function: PGItem::has_state_def // Access: Published // Description: Returns true if get_state_def() has ever been called // for the indicated state (thus defining a render // subgraph for this state index), false otherwise. //////////////////////////////////////////////////////////////////// bool PGItem:: has_state_def(int state) const { if (state < 0 || state >= (int)_state_defs.size()) { return false; } return (!_state_defs[state]._root.is_empty()); } //////////////////////////////////////////////////////////////////// // Function: PGItem::clear_state_def // Access: Published // Description: Resets the NodePath assigned to the indicated state // to its initial default, with only a frame // representation if appropriate. //////////////////////////////////////////////////////////////////// void PGItem:: clear_state_def(int state) { if (state < 0 || state >= (int)_state_defs.size()) { return; } _state_defs[state]._root = NodePath(); _state_defs[state]._frame = NodePath(); _state_defs[state]._frame_stale = true; mark_bound_stale(); } //////////////////////////////////////////////////////////////////// // Function: PGItem::get_state_def // Access: Published // Description: Returns the Node that is the root of the subgraph // that will be drawn when the PGItem is in the // indicated state. The first time this is called for a // particular state index, it may create the Node. //////////////////////////////////////////////////////////////////// NodePath &PGItem:: get_state_def(int state) { nassertr(state >= 0 && state < 1000, get_state_def(0)); // Sanity check. slot_state_def(state); if (_state_defs[state]._root.is_empty()) { // Create a new node. _state_defs[state]._root = NodePath("state_" + format_string(state)); _state_defs[state]._frame_stale = true; } if (_state_defs[state]._frame_stale) { update_frame(state); } return _state_defs[state]._root; } //////////////////////////////////////////////////////////////////// // Function: PGItem::instance_to_state_def // Access: Published // Description: Parents an instance of the bottom node of the // indicated NodePath to the indicated state index. //////////////////////////////////////////////////////////////////// NodePath PGItem:: instance_to_state_def(int state, const NodePath &path) { if (path.is_empty()) { // If the source is empty, quietly do nothing. return NodePath(); } mark_bound_stale(); return path.instance_to(get_state_def(state)); } //////////////////////////////////////////////////////////////////// // Function: PGItem::get_frame_style // Access: Published // Description: Returns the kind of frame that will be drawn behind // the item when it is in the indicated state. //////////////////////////////////////////////////////////////////// PGFrameStyle PGItem:: get_frame_style(int state) { if (state < 0 || state >= (int)_state_defs.size()) { return PGFrameStyle(); } return _state_defs[state]._frame_style; } //////////////////////////////////////////////////////////////////// // Function: PGItem::set_frame_style // Access: Published // Description: Changes the kind of frame that will be drawn behind // the item when it is in the indicated state. //////////////////////////////////////////////////////////////////// void PGItem:: set_frame_style(int state, const PGFrameStyle &style) { // Get the state def node, mainly to ensure that this state is // slotted and listed as having been defined. NodePath &root = get_state_def(state); nassertv(!root.is_empty()); _state_defs[state]._frame_style = style; _state_defs[state]._frame_stale = true; mark_bound_stale(); } #ifdef HAVE_AUDIO //////////////////////////////////////////////////////////////////// // Function: PGItem::set_sound // Access: Published // Description: Sets the sound that will be played whenever the // indicated event occurs. //////////////////////////////////////////////////////////////////// void PGItem:: set_sound(const string &event, AudioSound *sound) { _sounds[event] = sound; } //////////////////////////////////////////////////////////////////// // Function: PGItem::clear_sound // Access: Published // Description: Removes the sound associated with the indicated // event. //////////////////////////////////////////////////////////////////// void PGItem:: clear_sound(const string &event) { _sounds.erase(event); } //////////////////////////////////////////////////////////////////// // Function: PGItem::get_sound // Access: Published // Description: Returns the sound associated with the indicated // event, or NULL if there is no associated sound. //////////////////////////////////////////////////////////////////// AudioSound *PGItem:: get_sound(const string &event) const { Sounds::const_iterator si = _sounds.find(event); if (si != _sounds.end()) { return (*si).second; } return (AudioSound *)NULL; } //////////////////////////////////////////////////////////////////// // Function: PGItem::has_sound // Access: Published // Description: Returns true if there is a sound associated with the // indicated event, or false otherwise. //////////////////////////////////////////////////////////////////// bool PGItem:: has_sound(const string &event) const { return (_sounds.count(event) != 0); } #endif // HAVE_AUDIO //////////////////////////////////////////////////////////////////// // Function: PGItem::get_text_node // Access: Published, Static // Description: Returns the TextNode object that will be used by all // PGItems to generate default labels given a string. // This can be loaded with the default font, etc. //////////////////////////////////////////////////////////////////// TextNode *PGItem:: get_text_node() { if (_text_node == (TextNode *)NULL) { _text_node = new TextNode("pguiText"); _text_node->set_text_color(0.0f, 0.0f, 0.0f, 1.0f); // The default TextNode is aligned to the left, for the // convenience of PGEntry. _text_node->set_align(TextNode::A_left); } return _text_node; } //////////////////////////////////////////////////////////////////// // Function: PGItem::play_sound // Access: Protected // Description: Plays the sound associated with the indicated event, // if there is one. //////////////////////////////////////////////////////////////////// void PGItem:: play_sound(const string &event) { #ifdef HAVE_AUDIO Sounds::const_iterator si = _sounds.find(event); if (si != _sounds.end()) { AudioSound *sound = (*si).second; sound->play(); } #endif // HAVE_AUDIO } //////////////////////////////////////////////////////////////////// // Function: PGItem::slot_state_def // Access: Private // Description: Ensures there is a slot in the array for the given // state definition. //////////////////////////////////////////////////////////////////// void PGItem:: slot_state_def(int state) { while (state >= (int)_state_defs.size()) { StateDef def; def._frame_stale = true; _state_defs.push_back(def); } } //////////////////////////////////////////////////////////////////// // Function: PGItem::update_frame // Access: Private // Description: Generates a new instance of the frame geometry for // the indicated state. //////////////////////////////////////////////////////////////////// void PGItem:: update_frame(int state) { // First, remove the old frame geometry, if any. if (state >= 0 && state < (int)_state_defs.size()) { _state_defs[state]._frame.remove_node(); } // We must turn off the stale flag first, before we call // get_state_def(), to prevent get_state_def() from being a // recursive call. _state_defs[state]._frame_stale = false; // Now create new frame geometry. if (has_frame()) { NodePath &root = get_state_def(state); _state_defs[state]._frame = _state_defs[state]._frame_style.generate_into(root, _frame); } } //////////////////////////////////////////////////////////////////// // Function: PGItem::mark_frames_stale // Access: Private // Description: Marks all the frames in all states stale, so that // they will be regenerated the next time each state is // requested. //////////////////////////////////////////////////////////////////// void PGItem:: mark_frames_stale() { StateDefs::iterator di; for (di = _state_defs.begin(); di != _state_defs.end(); ++di) { // Remove the old frame, if any. (*di)._frame.remove_node(); (*di)._frame_stale = true; } mark_bound_stale(); }