diff --git a/panda/src/pgui/pgCullTraverser.I b/panda/src/pgui/pgCullTraverser.I new file mode 100644 index 0000000000..0db24ff008 --- /dev/null +++ b/panda/src/pgui/pgCullTraverser.I @@ -0,0 +1,31 @@ +// Filename: pgCullTraverser.I +// Created by: drose (14Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: PGCullTraverser::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PGCullTraverser:: +PGCullTraverser(qpPGTop *top, qpCullTraverser *trav) : + qpCullTraverser(*trav), + _top(top) +{ + _sort_index = 0; +} diff --git a/panda/src/pgui/pgCullTraverser.cxx b/panda/src/pgui/pgCullTraverser.cxx new file mode 100644 index 0000000000..60971f3edc --- /dev/null +++ b/panda/src/pgui/pgCullTraverser.cxx @@ -0,0 +1,21 @@ +// Filename: pgCullTraverser.cxx +// Created by: drose (14Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "pgCullTraverser.h" + +TypeHandle PGCullTraverser::_type_handle; diff --git a/panda/src/pgui/pgCullTraverser.h b/panda/src/pgui/pgCullTraverser.h new file mode 100644 index 0000000000..9c88ed2cf1 --- /dev/null +++ b/panda/src/pgui/pgCullTraverser.h @@ -0,0 +1,66 @@ +// Filename: pgCullTraverser.h +// Created by: drose (14Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef PGCULLTRAVERSER_H +#define PGCULLTRAVERSER_H + +#include "pandabase.h" + +#include "qppgTop.h" +#include "qpcullTraverser.h" + +//////////////////////////////////////////////////////////////////// +// Class : PGCullTraverser +// Description : This is a specialization of CullTraverser for use +// within the pgui system. It is substituted in for the +// normal CullTraverser by the PGTop node. Its purpose +// is to carry additional data through the traversal so +// that PGItems can know how to register their regions +// with the current MouseWatcherGroup. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA PGCullTraverser : public qpCullTraverser { +public: + INLINE PGCullTraverser(qpPGTop *top, qpCullTraverser *trav); + + qpPGTop *_top; + int _sort_index; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + qpCullTraverser::init_type(); + register_type(_type_handle, "PGCullTraverser", + qpCullTraverser::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "pgCullTraverser.I" + +#endif + + + diff --git a/panda/src/pgui/qppgButton.I b/panda/src/pgui/qppgButton.I new file mode 100644 index 0000000000..25eca20578 --- /dev/null +++ b/panda/src/pgui/qppgButton.I @@ -0,0 +1,75 @@ +// Filename: qppgButton.I +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::setup +// Access: Published +// Description: Sets up the button using the indicated NodePath as +// arbitrary geometry. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGButton:: +setup(const qpNodePath &ready) { + setup(ready, ready, ready, ready); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::setup +// Access: Published +// Description: Sets up the button using the indicated NodePath as +// arbitrary geometry. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGButton:: +setup(const qpNodePath &ready, const qpNodePath &depressed) { + setup(ready, depressed, ready, ready); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::setup +// Access: Published +// Description: Sets up the button using the indicated NodePath as +// arbitrary geometry. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGButton:: +setup(const qpNodePath &ready, const qpNodePath &depressed, + const qpNodePath &rollover) { + setup(ready, depressed, rollover, ready); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::get_click_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the click +// event for all qpPGButtons. The click event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGButton:: +get_click_prefix() { + return "click-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::get_click_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// button is clicked normally. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGButton:: +get_click_event(const ButtonHandle &button) const { + return "click-" + button.get_name() + "-" + get_id(); +} diff --git a/panda/src/pgui/qppgButton.cxx b/panda/src/pgui/qppgButton.cxx new file mode 100644 index 0000000000..5daae15190 --- /dev/null +++ b/panda/src/pgui/qppgButton.cxx @@ -0,0 +1,291 @@ +// Filename: qppgButton.cxx +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "qppgButton.h" +#include "pgMouseWatcherParameter.h" + +#include "throw_event.h" +#include "mouseButton.h" +#include "mouseWatcherParameter.h" +#include "colorAttrib.h" +#include "transformState.h" + +TypeHandle qpPGButton::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpPGButton:: +qpPGButton(const string &name) : qpPGItem(name) +{ + _button_down = false; + _click_buttons.insert(MouseButton::one()); + + set_active(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +qpPGButton:: +~qpPGButton() { +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +qpPGButton:: +qpPGButton(const qpPGButton ©) : + qpPGItem(copy) +{ + _button_down = false; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::make_copy +// Access: Public, 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 *qpPGButton:: +make_copy() const { + return new qpPGButton(*this); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::enter +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// mouse enters the region. +//////////////////////////////////////////////////////////////////// +void qpPGButton:: +enter(const MouseWatcherParameter ¶m) { + if (get_active()) { + set_state(_button_down ? S_depressed : S_rollover); + } + qpPGItem::enter(param); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::exit +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// mouse exits the region. +//////////////////////////////////////////////////////////////////// +void qpPGButton:: +exit(const MouseWatcherParameter ¶m) { + if (get_active()) { + set_state(S_ready); + } + qpPGItem::exit(param); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::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 qpPGButton:: +press(const MouseWatcherParameter ¶m, bool background) { + if (has_click_button(param.get_button())) { + if (get_active()) { + _button_down = true; + set_state(S_depressed); + } + } + qpPGItem::press(param, background); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::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 qpPGButton:: +release(const MouseWatcherParameter ¶m, bool background) { + if (has_click_button(param.get_button())) { + _button_down = false; + if (get_active()) { + if (param.is_outside()) { + set_state(S_ready); + } else { + set_state(S_rollover); + click(param); + } + } + } + qpPGItem::release(param, background); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::click +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// button is clicked down-and-up by the user normally. +//////////////////////////////////////////////////////////////////// +void qpPGButton:: +click(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_click_event(param.get_button()); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::setup +// Access: Published +// Description: Sets up the button as a default text button using the +// indicated label string. The TextNode defined by +// PGItem::get_text_node() will be used to create the +// label geometry. This automatically sets up the frame +// according to the size of the text. +//////////////////////////////////////////////////////////////////// +void qpPGButton:: +setup(const string &label) { + clear_state_def(S_ready); + clear_state_def(S_depressed); + clear_state_def(S_rollover); + clear_state_def(S_inactive); + + qpTextNode *text_node = get_text_node(); + text_node->set_text(label); + PT(PandaNode) geom = text_node->generate(); + + LVecBase4f frame = text_node->get_card_actual(); + set_frame(frame[0] - 0.4f, frame[1] + 0.4f, frame[2] - 0.15f, frame[3] + 0.15f); + + PT(PandaNode) ready = new PandaNode("ready"); + PT(PandaNode) depressed = new PandaNode("depressed"); + PT(PandaNode) rollover = new PandaNode("rollover"); + PT(PandaNode) inactive = new PandaNode("inactive"); + + get_state_def(S_ready)->add_child(ready); + get_state_def(S_depressed)->add_child(depressed); + get_state_def(S_rollover)->add_child(rollover); + get_state_def(S_inactive)->add_child(inactive); + + ready->add_child(geom); + depressed->add_child(geom); + rollover->add_child(geom); + inactive->add_child(geom); + + PGFrameStyle style; + style.set_color(0.8f, 0.8f, 0.8f, 1.0); + style.set_width(0.1f, 0.1f); + + style.set_type(PGFrameStyle::T_bevel_out); + set_frame_style(S_ready, style); + + style.set_color(0.9f, 0.9f, 0.9f, 1.0); + set_frame_style(S_rollover, style); + + inactive->set_attrib(ColorAttrib::make_flat(Colorf(0.8f, 0.8f, 0.8f, 1.0f))); + style.set_color(0.6f, 0.6f, 0.6f, 1.0); + set_frame_style(S_inactive, style); + + style.set_type(PGFrameStyle::T_bevel_in); + style.set_color(0.8f, 0.8f, 0.8f, 1.0); + set_frame_style(S_depressed, style); + depressed->set_transform(TransformState::make_pos(LVector3f(0.05f, 0.0f, -0.05f))); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::setup +// Access: Published +// Description: Sets up the button using the indicated NodePath as +// arbitrary geometry. +//////////////////////////////////////////////////////////////////// +void qpPGButton:: +setup(const qpNodePath &ready, const qpNodePath &depressed, + const qpNodePath &rollover, const qpNodePath &inactive) { + clear_state_def(S_ready); + clear_state_def(S_depressed); + clear_state_def(S_rollover); + clear_state_def(S_inactive); + + instance_to_state_def(S_ready, ready); + instance_to_state_def(S_depressed, depressed); + instance_to_state_def(S_rollover, rollover); + instance_to_state_def(S_inactive, inactive); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::set_active +// Access: Published, Virtual +// Description: Toggles the active/inactive state of the button. In +// the case of a qpPGButton, this also changes its visual +// appearance. +//////////////////////////////////////////////////////////////////// +void qpPGButton:: +set_active(bool active) { + if (active != get_active()) { + qpPGItem::set_active(active); + set_state(active ? S_ready : S_inactive); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::add_click_button +// Access: Published +// Description: Adds the indicated button to the set of buttons that +// can effectively "click" the qpPGButton. Normally, this +// is just MouseButton::one(). Returns true if the +// button was added, or false if it was already there. +//////////////////////////////////////////////////////////////////// +bool qpPGButton:: +add_click_button(const ButtonHandle &button) { + return _click_buttons.insert(button).second; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::remove_click_button +// Access: Published +// Description: Removes the indicated button from the set of buttons +// that can effectively "click" the qpPGButton. Normally, +// this is just MouseButton::one(). Returns true if the +// button was removed, or false if it was not in the +// set. +//////////////////////////////////////////////////////////////////// +bool qpPGButton:: +remove_click_button(const ButtonHandle &button) { + return (_click_buttons.erase(button) != 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGButton::has_click_button +// Access: Published +// Description: Returns true if the indicated button is on the set of +// buttons that can effectively "click" the qpPGButton. +// Normally, this is just MouseButton::one(). +//////////////////////////////////////////////////////////////////// +bool qpPGButton:: +has_click_button(const ButtonHandle &button) { + return (_click_buttons.count(button) != 0); +} diff --git a/panda/src/pgui/qppgButton.h b/panda/src/pgui/qppgButton.h new file mode 100644 index 0000000000..d800ef8c35 --- /dev/null +++ b/panda/src/pgui/qppgButton.h @@ -0,0 +1,104 @@ +// Filename: qppgButton.h +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef qpPGBUTTON_H +#define qpPGBUTTON_H + +#include "pandabase.h" + +#include "qppgItem.h" +#include "qpnodePath.h" +#include "pset.h" + +//////////////////////////////////////////////////////////////////// +// Class : qpPGButton +// Description : This is a particular kind of PGItem that is +// specialized to behave like a normal button object. +// It keeps track of its own state, and handles mouse +// events sensibly. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA qpPGButton : public qpPGItem { +PUBLISHED: + qpPGButton(const string &name); + virtual ~qpPGButton(); + +protected: + qpPGButton(const qpPGButton ©); + +public: + virtual PandaNode *make_copy() const; + + virtual void enter(const MouseWatcherParameter ¶m); + virtual void exit(const MouseWatcherParameter ¶m); + virtual void press(const MouseWatcherParameter ¶m, bool background); + virtual void release(const MouseWatcherParameter ¶m, bool background); + + virtual void click(const MouseWatcherParameter ¶m); + +PUBLISHED: + enum State { + S_ready = 0, + S_depressed, + S_rollover, + S_inactive + }; + + void setup(const string &label); + INLINE void setup(const qpNodePath &ready); + INLINE void setup(const qpNodePath &ready, const qpNodePath &depressed); + INLINE void setup(const qpNodePath &ready, const qpNodePath &depressed, + const qpNodePath &rollover); + void setup(const qpNodePath &ready, const qpNodePath &depressed, + const qpNodePath &rollover, const qpNodePath &inactive); + + virtual void set_active(bool active); + + bool add_click_button(const ButtonHandle &button); + bool remove_click_button(const ButtonHandle &button); + bool has_click_button(const ButtonHandle &button); + + INLINE static string get_click_prefix(); + INLINE string get_click_event(const ButtonHandle &button) const; + +private: + typedef pset Buttons; + Buttons _click_buttons; + + bool _button_down; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + qpPGItem::init_type(); + register_type(_type_handle, "qpPGButton", + qpPGItem::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "qppgButton.I" + +#endif diff --git a/panda/src/pgui/qppgEntry.I b/panda/src/pgui/qppgEntry.I new file mode 100644 index 0000000000..d1af9980b7 --- /dev/null +++ b/panda/src/pgui/qppgEntry.I @@ -0,0 +1,384 @@ +// Filename: qppgEntry.I +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_text +// Access: Published +// Description: Changes the text currently displayed within the +// entry. This uses the Unicode encoding currently +// specified for the "focus" TextNode; therefore, the +// TextNode must exist before calling set_text(). +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_text(const string &text) { + qpTextNode *text_node = get_text_def(S_focus); + nassertv(text_node != (qpTextNode *)NULL); + set_wtext(text_node->decode_text(text)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_text +// Access: Published +// Description: Returns the text currently displayed within the +// entry. This uses the Unicode encoding currently +// specified for the "focus" TextNode; therefore, the +// TextNode must exist before calling get_text(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_text() const { + qpTextNode *text_node = get_text_def(S_focus); + nassertr(text_node != (qpTextNode *)NULL, string()); + return text_node->encode_wtext(get_wtext()); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_cursor_position +// Access: Published +// Description: Sets the current position of the cursor. This is the +// position within the text at which the next letter +// typed by the user will be inserted; normally it is +// the same as the length of the text. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_cursor_position(int position) { + if (_cursor_position != position) { + _cursor_position = position; + _cursor_stale = true; + _blink_start = ClockObject::get_global_clock()->get_frame_time(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_cursor_position +// Access: Published +// Description: Returns the current position of the cursor. +//////////////////////////////////////////////////////////////////// +INLINE int qpPGEntry:: +get_cursor_position() const { + return _cursor_position; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_max_chars +// Access: Published +// Description: Sets the maximum number of characters that may be +// typed into the entry. This is a limit on the number +// of characters, as opposed to the width of the entry; +// see also set_max_width(). +// +// If this is 0, there is no limit. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_max_chars(int max_chars) { + _max_chars = max_chars; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_max_chars +// Access: Published +// Description: Returns the current maximum number of characters that +// may be typed into the entry, or 0 if there is no +// limit. See set_max_chars(). +//////////////////////////////////////////////////////////////////// +INLINE int qpPGEntry:: +get_max_chars() const { + return _max_chars; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_max_width +// Access: Published +// Description: Sets the maximum width of all characters that may be +// typed into the entry. This is a limit on the width +// of the formatted text, not a fixed limit on the +// number of characters; also set_max_chars(). +// +// If this is 0, there is no limit. +// +// If _num_lines is more than 1, rather than being a +// fixed width on the whole entry, this becomes instead +// the wordwrap width (and the width limit on the entry +// is essentially _max_width * _num_lines). +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_max_width(float max_width) { + _max_width = max_width; + _text_geom_stale = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_max_width +// Access: Published +// Description: Returns the current maximum width of the characters +// that may be typed into the entry, or 0 if there is no +// limit. See set_max_width(). +//////////////////////////////////////////////////////////////////// +INLINE float qpPGEntry:: +get_max_width() const { + return _max_width; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_num_lines +// Access: Published +// Description: Sets the number of lines of text the qpPGEntry will +// use. This only has meaning if _max_width is not 0; +// _max_width indicates the wordwrap width of each line. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_num_lines(int num_lines) { + nassertv(num_lines >= 1); + _num_lines = num_lines; + _text_geom_stale = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_num_lines +// Access: Published +// Description: Returns the number of lines of text the qpPGEntry will +// use, if _max_width is not 0. See set_num_lines(). +//////////////////////////////////////////////////////////////////// +INLINE int qpPGEntry:: +get_num_lines() const { + return _num_lines; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_blink_rate +// Access: Published +// Description: Sets the number of times per second the cursor will +// blink while the entry has keyboard focus. +// +// If this is 0, the cursor does not blink, but is held +// steady. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_blink_rate(float blink_rate) { + _blink_rate = blink_rate; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_blink_rate +// Access: Published +// Description: Returns the number of times per second the cursor +// will blink, or 0 if the cursor is not to blink. +//////////////////////////////////////////////////////////////////// +INLINE float qpPGEntry:: +get_blink_rate() const { + return _blink_rate; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_cursor_def +// Access: Published +// Description: Returns the Node that will be rendered to represent +// the cursor. You can attach suitable cursor geometry +// to this node. +//////////////////////////////////////////////////////////////////// +INLINE PandaNode *qpPGEntry:: +get_cursor_def() { + return _cursor_def; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::clear_cursor_def +// Access: Published +// Description: Removes all the children from the cursor_def node, in +// preparation for adding a new definition. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +clear_cursor_def() { + get_cursor_def()->remove_all_children(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_cursor_keys_active +// Access: Published +// Description: Sets whether the arrow keys (and home/end) control +// movement of the cursor. If true, they are active; if +// false, they are ignored. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_cursor_keys_active(bool flag) { + _cursor_keys_active = flag; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_cursor_keys_active +// Access: Published +// Description: Returns whether the arrow keys are currently set to +// control movement of the cursor; see +// set_cursor_keys_active(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGEntry:: +get_cursor_keys_active() const { + return _cursor_keys_active; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_obscure_mode +// Access: Published +// Description: Specifies whether obscure mode should be enabled. In +// obscure mode, a string of asterisks is displayed +// instead of the literal text, e.g. for entering +// passwords. +// +// In obscure mode, the width of the text is computed +// based on the width of the string of asterisks, not on +// the width of the actual text. This has implications +// on the maximum length of text that may be entered if +// max_width is in effect. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_obscure_mode(bool flag) { + if (_obscure_mode != flag) { + _obscure_mode = flag; + _text_geom_stale = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_obscure_mode +// Access: Published +// Description: Specifies whether obscure mode is enabled. See +// set_obscure_mode(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGEntry:: +get_obscure_mode() const { + return _obscure_mode; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_accept_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the accept +// event for all PGEntries. The accept event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_accept_prefix() { + return "accept-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_overflow_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the overflow +// event for all PGEntries. The overflow event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_overflow_prefix() { + return "overflow-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_type_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the type +// event for all PGEntries. The type event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_type_prefix() { + return "type-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_erase_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the erase +// event for all PGEntries. The erase event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_erase_prefix() { + return "erase-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_accept_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// entry is accepted normally. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_accept_event(const ButtonHandle &button) const { + return "accept-" + button.get_name() + "-" + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_overflow_event +// Access: Published +// Description: Returns the event name that will be thrown when too +// much text is attempted to be entered into the +// qpPGEntry, exceeding either the limit set via +// set_max_chars() or via set_max_width(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_overflow_event() const { + return "overflow-" + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_type_event +// Access: Published +// Description: Returns the event name that will be thrown whenever +// the user extends the text by typing. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_type_event() const { + return "type-" + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_erase_event +// Access: Published +// Description: Returns the event name that will be thrown whenever +// the user erases characters in the text. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGEntry:: +get_erase_event() const { + return "erase-" + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_wtext +// Access: Public +// Description: Changes the text currently displayed within the +// entry. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGEntry:: +set_wtext(const wstring &wtext) { + _wtext = wtext; + _text_geom_stale = true; + _cursor_stale = true; + _blink_start = ClockObject::get_global_clock()->get_frame_time(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_wtext +// Access: Public +// Description: Returns the text currently displayed within the +// entry. +//////////////////////////////////////////////////////////////////// +INLINE const wstring &qpPGEntry:: +get_wtext() const { + return _wtext; +} diff --git a/panda/src/pgui/qppgEntry.cxx b/panda/src/pgui/qppgEntry.cxx new file mode 100644 index 0000000000..9f2c4bc5e6 --- /dev/null +++ b/panda/src/pgui/qppgEntry.cxx @@ -0,0 +1,881 @@ +// Filename: qppgEntry.cxx +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "qppgEntry.h" +#include "pgMouseWatcherParameter.h" + +#include "qpcullTraverser.h" +#include "throw_event.h" +#include "transformState.h" +#include "mouseWatcherParameter.h" +#include "keyboardButton.h" +#include "mouseButton.h" +#include "lineSegs.h" + +#include "math.h" + +TypeHandle qpPGEntry::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpPGEntry:: +qpPGEntry(const string &name) : + qpPGItem(name) +{ + _cursor_position = 0; + _cursor_stale = true; + _max_chars = 0; + _max_width = 0.0f; + _num_lines = 1; + _last_text_def = (qpTextNode *)NULL; + _text_geom_stale = true; + _blink_start = 0.0f; + _blink_rate = 1.0f; + + _text_render_root = new PandaNode("text_root"); + _current_text_node = (PandaNode *)NULL; + _cursor_def = new PandaNode("cursor"); + _text_render_root->add_child(_cursor_def); + _cursor_visible = true; + + _cursor_keys_active = true; + _obscure_mode = false; + + set_active(true); + update_state(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +qpPGEntry:: +~qpPGEntry() { +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::Copy Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +qpPGEntry:: +qpPGEntry(const qpPGEntry ©) : + qpPGItem(copy), + _wtext(copy._wtext), + _obscured_wtext(copy._obscured_wtext), + _cursor_position(copy._cursor_position), + _max_chars(copy._max_chars), + _max_width(copy._max_width), + _text_defs(copy._text_defs), + _blink_start(copy._blink_start), + _blink_rate(copy._blink_rate), + _cursor_keys_active(copy._cursor_keys_active), + _obscure_mode(copy._obscure_mode) +{ + _cursor_stale = true; + _last_text_def = (qpTextNode *)NULL; + _text_geom_stale = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::make_copy +// Access: Public, 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 *qpPGEntry:: +make_copy() const { + return new qpPGEntry(*this); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::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 qpPGEntry:: +has_cull_callback() const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::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 qpPGEntry:: +cull_callback(qpCullTraverser *trav, CullTraverserData &data) { + qpPGItem::cull_callback(trav, data); + update_text(); + update_cursor(); + + // Now render the text. + nassertr(_text_render_root != (PandaNode *)NULL, true); + trav->traverse(_text_render_root, data); + + // Now continue to render everything else below this node. + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::press +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever a +// mouse or keyboard entry is depressed while the mouse +// is within the region. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +press(const MouseWatcherParameter ¶m, bool background) { + if (get_active()) { + if (param.has_button()) { + ButtonHandle button = param.get_button(); + + if (button == MouseButton::one() || + button == MouseButton::two() || + button == MouseButton::three()) { + // Mouse button; set focus. + set_focus(true); + + } else if ((!background && get_focus()) || + (background && get_background_focus())) { + // Keyboard button. + _cursor_position = min(_cursor_position, (int)_wtext.length()); + _blink_start = ClockObject::get_global_clock()->get_frame_time(); + if (button == KeyboardButton::enter()) { + // Enter. Accept the entry. + accept(param); + + } else if (button == KeyboardButton::backspace()) { + // Backspace. Remove the character to the left of the cursor. + if (_cursor_position > 0) { + if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) { + _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1); + } + _wtext.erase(_wtext.begin() + _cursor_position - 1); + _cursor_position--; + _cursor_stale = true; + _text_geom_stale = true; + erase(param); + } + + } else if (button == KeyboardButton::del()) { + // Delete. Remove the character to the right of the cursor. + if (_cursor_position < (int)_wtext.length()) { + if (_obscure_mode && _obscured_wtext.length() == _wtext.length()) { + _obscured_wtext.erase(_obscured_wtext.begin() + _obscured_wtext.length() - 1); + } + _wtext.erase(_wtext.begin() + _cursor_position); + _text_geom_stale = true; + erase(param); + } + + } else if (button == KeyboardButton::left()) { + if (_cursor_keys_active) { + // Left arrow. Move the cursor position to the left. + _cursor_position = max(_cursor_position - 1, 0); + _cursor_stale = true; + } + + } else if (button == KeyboardButton::right()) { + if (_cursor_keys_active) { + // Right arrow. Move the cursor position to the right. + _cursor_position = min(_cursor_position + 1, (int)_wtext.length()); + _cursor_stale = true; + } + + } else if (button == KeyboardButton::home()) { + if (_cursor_keys_active) { + // Home. Move the cursor position to the beginning. + _cursor_position = 0; + _cursor_stale = true; + } + + } else if (button == KeyboardButton::end()) { + if (_cursor_keys_active) { + // End. Move the cursor position to the end. + _cursor_position = _wtext.length(); + _cursor_stale = true; + } + + } else if (!use_keystrokes && button.has_ascii_equivalent()) { + // This part of the code is deprecated and will be removed + // soon. It only supports the old button up/down method of + // sending keystrokes, instead of the new keystroke method. + wchar_t key = button.get_ascii_equivalent(); + if (isprint(key)) { + // A normal visible character. Add a new character to the + // text entry, if there's room. + + if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) { + overflow(param); + } else { + wstring new_text = + _wtext.substr(0, _cursor_position) + key + + _wtext.substr(_cursor_position); + + // Get a string to measure its length. In normal mode, + // we measure the text itself. In obscure mode, we + // measure a string of n asterisks. + wstring measure_text; + if (_obscure_mode) { + measure_text = get_display_wtext() + (wchar_t)'*'; + } else { + measure_text = new_text; + } + + // Check the length. + bool too_long = false; + if (_max_width > 0.0f) { + qpTextNode *text_node = get_text_def(S_focus); + if (_num_lines <= 1) { + // If we have only one line, we can check the length + // by simply measuring the width of the text. + too_long = (text_node->calc_width(measure_text) > _max_width); + + } else { + // If we have multiple lines, we have to check the + // length by wordwrapping it and counting up the + // number of lines. + wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true); + int num_lines = 1; + size_t last_line_start = 0; + for (size_t p = 0; + p < ww_text.length() && !too_long; + ++p) { + if (ww_text[p] == '\n') { + last_line_start = p + 1; + num_lines++; + too_long = (num_lines > _num_lines); + } + } + + if (!too_long) { + // We must also ensure that the last line is not too + // long (it might be, because of additional + // whitespace on the end). + wstring last_line = ww_text.substr(last_line_start); + float last_line_width = text_node->calc_width(last_line); + if (num_lines == _num_lines) { + // Mainly we only care about this if we're on + // the very last line. + too_long = (last_line_width > _max_width); + + } else { + // If we're not on the very last line, the width + // is still important, just so we don't allow an + // infinite number of spaces to accumulate. + // However, we must allow at least *one* space + // on the end of a line. + if (_wtext.length() >= 1 && + _wtext[_wtext.length() - 1] == ' ') { + if (last_line_width > _max_width) { + // In this case, however, it's not exactly + // an overflow; we just want to reject the + // space. + return; + } + } + } + } + } + } + + if (too_long) { + overflow(param); + + } else { + _wtext = new_text; + if (_obscure_mode) { + _obscured_wtext = measure_text; + } + + _cursor_position++; + _cursor_stale = true; + _text_geom_stale = true; + type(param); + } + } + } + } + } + } + } + qpPGItem::press(param, background); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::keystroke +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever +// the user types a key. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +keystroke(const MouseWatcherParameter ¶m, bool background) { + if (get_active()) { + if (param.has_keycode()) { + int keycode = param.get_keycode(); + + if (use_keystrokes) { + if (!isascii(keycode) || isprint(keycode)) { + // A normal visible character. Add a new character to the + // text entry, if there's room. + wstring new_char(1, (wchar_t)keycode); + + if (get_max_chars() > 0 && (int)_wtext.length() >= get_max_chars()) { + overflow(param); + } else { + _cursor_position = min(_cursor_position, (int)_wtext.length()); + wstring new_text = + _wtext.substr(0, _cursor_position) + new_char + + _wtext.substr(_cursor_position); + + // Get a string to measure its length. In normal mode, + // we measure the text itself. In obscure mode, we + // measure a string of n asterisks. + wstring measure_text; + if (_obscure_mode) { + measure_text = get_display_wtext() + (wchar_t)'*'; + } else { + measure_text = new_text; + } + + // Check the length. + bool too_long = false; + if (_max_width > 0.0f) { + qpTextNode *text_node = get_text_def(S_focus); + if (_num_lines <= 1) { + // If we have only one line, we can check the length + // by simply measuring the width of the text. + too_long = (text_node->calc_width(measure_text) > _max_width); + + } else { + // If we have multiple lines, we have to check the + // length by wordwrapping it and counting up the + // number of lines. + wstring ww_text = text_node->wordwrap_to(measure_text, _max_width, true); + int num_lines = 1; + size_t last_line_start = 0; + for (size_t p = 0; + p < ww_text.length() && !too_long; + ++p) { + if (ww_text[p] == '\n') { + last_line_start = p + 1; + num_lines++; + too_long = (num_lines > _num_lines); + } + } + + if (!too_long) { + // We must also ensure that the last line is not too + // long (it might be, because of additional + // whitespace on the end). + wstring last_line = ww_text.substr(last_line_start); + float last_line_width = text_node->calc_width(last_line); + if (num_lines == _num_lines) { + // Mainly we only care about this if we're on + // the very last line. + too_long = (last_line_width > _max_width); + + } else { + // If we're not on the very last line, the width + // is still important, just so we don't allow an + // infinite number of spaces to accumulate. + // However, we must allow at least *one* space + // on the end of a line. + if (_wtext.length() >= 1 && + _wtext[_wtext.length() - 1] == ' ') { + if (last_line_width > _max_width) { + // In this case, however, it's not exactly + // an overflow; we just want to reject the + // space. + return; + } + } + } + } + } + } + + if (too_long) { + overflow(param); + + } else { + _wtext = new_text; + if (_obscure_mode) { + _obscured_wtext = measure_text; + } + + _cursor_position += new_char.length(); + _cursor_stale = true; + _text_geom_stale = true; + type(param); + } + } + } + } + } + } + qpPGItem::keystroke(param, background); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::accept +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// entry is accepted by the user pressing Enter normally. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +accept(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_accept_event(param.get_button()); + play_sound(event); + throw_event(event, EventParameter(ep)); + set_focus(false); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::overflow +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// entry is overflowed because the user attempts to type +// too many characters, exceeding either set_max_chars() +// or set_max_width(). +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +overflow(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_overflow_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::type +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// user extends the text by typing. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +type(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_type_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::erase +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// user erase characters in the text. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +erase(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_erase_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::setup +// Access: Published +// Description: Sets up the entry for normal use. The width is the +// maximum width of characters that will be typed, and +// num_lines is the integer number of lines of text of +// the entry. Both of these together determine the size +// of the entry, based on the qpTextNode in effect. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +setup(float width, int num_lines) { + set_text(string()); + _cursor_position = 0; + set_max_chars(0); + set_max_width(width); + set_num_lines(num_lines); + + qpTextNode *text_node = get_text_def(S_focus); + float line_height = text_node->get_line_height(); + + // Define determine the four corners of the frame. + LPoint3f ll(0.0f, 0.0f, -0.3f * line_height - (line_height * (num_lines - 1))); + LPoint3f ur(width, 0.0f, line_height); + LPoint3f lr(ur[0], 0.0f, ll[2]); + LPoint3f ul(ll[0], 0.0f, ur[2]); + + // Transform each corner by the qpTextNode's transform. + LMatrix4f mat = text_node->get_transform(); + ll = ll * mat; + ur = ur * mat; + lr = lr * mat; + ul = ul * mat; + + // And get the new minmax to define the frame. We do all this work + // instead of just using the lower-left and upper-right corners, + // just in case the text was rotated. + LVecBase4f frame; + frame[0] = min(min(ll[0], ur[0]), min(lr[0], ul[0])); + frame[1] = max(max(ll[0], ur[0]), max(lr[0], ul[0])); + frame[2] = min(min(ll[2], ur[2]), min(lr[2], ul[2])); + frame[3] = max(max(ll[2], ur[2]), max(lr[2], ul[2])); + + switch (text_node->get_align()) { + case qpTextNode::A_left: + // The default case. + break; + + case qpTextNode::A_center: + frame[0] = -width / 2.0; + frame[1] = width / 2.0; + break; + + case qpTextNode::A_right: + frame[0] = -width; + frame[1] = 0.0f; + break; + } + + set_frame(frame[0] - 0.15f, frame[1] + 0.15f, frame[2], frame[3]); + + PGFrameStyle style; + style.set_width(0.1f, 0.1f); + style.set_type(PGFrameStyle::T_bevel_in); + style.set_color(0.8f, 0.8f, 0.8f, 1.0f); + + set_frame_style(S_no_focus, style); + + style.set_color(0.9f, 0.9f, 0.9f, 1.0f); + set_frame_style(S_focus, style); + + style.set_color(0.6f, 0.6f, 0.6f, 1.0f); + set_frame_style(S_inactive, style); + + // Set up a default cursor: a vertical bar. + clear_cursor_def(); + + /* + LineSegs ls; + ls.set_color(0.0f, 0.0f, 0.0f, 1.0f); + ls.move_to(0.0f, 0.0f, -0.15f * line_height); + ls.draw_to(0.0f, 0.0f, 0.85f * line_height); + new RenderRelation(get_cursor_def(), ls.create()); + */ + + // An underscore cursor would work too. + text_node->set_text("_"); + get_cursor_def()->add_child(text_node->generate()); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_text_def +// Access: Published +// Description: Changes the qpTextNode that will be used to render the +// text within the entry when the entry is in the +// indicated state. The default if nothing is specified +// is the same qpTextNode returned by +// qpPGItem::get_text_node(). +// +// It is the responsibility of the user to ensure that +// this qpTextNode has been frozen by a call to freeze(). +// Passing in an unfrozen qpTextNode will result in +// needless work. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +set_text_def(int state, qpTextNode *node) { + nassertv(state >= 0 && state < 1000); // Sanity check. + if (node == (qpTextNode *)NULL && state >= (int)_text_defs.size()) { + // If we're setting it to NULL, we don't need to slot a new one. + return; + } + slot_text_def(state); + + _text_defs[state] = node; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_text_def +// Access: Published +// Description: Returns the qpTextNode that will be used to render the +// text within the entry when the entry is in the +// indicated state. See set_text_def(). +//////////////////////////////////////////////////////////////////// +qpTextNode *qpPGEntry:: +get_text_def(int state) const { + if (state < 0 || state >= (int)_text_defs.size()) { + // If we don't have a definition, use the global one. + return get_text_node(); + } + if (_text_defs[state] == (qpTextNode *)NULL) { + return get_text_node(); + } + return _text_defs[state]; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_active +// Access: Published, Virtual +// Description: Toggles the active/inactive state of the entry. In +// the case of a qpPGEntry, this also changes its visual +// appearance. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +set_active(bool active) { + qpPGItem::set_active(active); + update_state(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::set_focus +// Access: Published, Virtual +// Description: Toggles the focus state of the entry. In the case of +// a qpPGEntry, this also changes its visual appearance. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +set_focus(bool focus) { + qpPGItem::set_focus(focus); + _blink_start = ClockObject::get_global_clock()->get_frame_time(); + update_state(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::get_display_wtext +// Access: Private +// Description: Returns the string that should be displayed within +// the entry. This is normally _wtext, but it may be +// _obscured_wtext. +//////////////////////////////////////////////////////////////////// +const wstring &qpPGEntry:: +get_display_wtext() { + if (_obscure_mode) { + // If obscure mode is enabled, we should just display a bunch of + // asterisks. + if (_obscured_wtext.length() != _wtext.length()) { + _obscured_wtext = wstring(); + wstring::const_iterator ti; + for (ti = _wtext.begin(); ti != _wtext.end(); ++ti) { + _obscured_wtext += (wchar_t)'*'; + } + } + + return _obscured_wtext; + + } else { + // In normal, non-obscure mode, we display the actual text. + return _wtext; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::slot_text_def +// Access: Private +// Description: Ensures there is a slot in the array for the given +// text definition. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +slot_text_def(int state) { + while (state >= (int)_text_defs.size()) { + _text_defs.push_back((qpTextNode *)NULL); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::update_text +// Access: Private +// Description: Causes the qpPGEntry to recompute its text, if +// necessary. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +update_text() { + qpTextNode *node = get_text_def(get_state()); + nassertv(node != (qpTextNode *)NULL); + + if (_text_geom_stale || node != _last_text_def) { + const wstring &display_wtext = get_display_wtext(); + + // We need to regenerate. + _last_text_def = node; + + if (_max_width > 0.0f && _num_lines > 1) { + // Fold the text into multiple lines. + wstring ww_text = + _last_text_def->wordwrap_to(display_wtext, _max_width, true); + + // And chop the lines up into pieces. + _ww_lines.clear(); + size_t p = 0; + size_t q = ww_text.find((wchar_t)'\n'); + while (q != string::npos) { + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = ww_text.substr(p, q - p); + + // Get the left edge of the text at this line. + line._left = 0.0f; + if (_last_text_def->get_align() != qpTextNode::A_left) { + _last_text_def->set_wtext(line._str); + line._left = _last_text_def->get_left(); + } + + p = q + 1; + q = ww_text.find('\n', p); + } + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = ww_text.substr(p); + + // Get the left edge of the text at this line. + line._left = 0.0f; + if (_last_text_def->get_align() != qpTextNode::A_left) { + _last_text_def->set_wtext(line._str); + line._left = _last_text_def->get_left(); + } + + _last_text_def->set_wtext(ww_text); + + } else { + // Only one line. + _ww_lines.clear(); + _ww_lines.push_back(WWLine()); + WWLine &line = _ww_lines.back(); + line._str = display_wtext; + + _last_text_def->set_wtext(display_wtext); + line._left = _last_text_def->get_left(); + } + + if (_current_text_node != (PandaNode *)NULL) { + _text_render_root->remove_child(_current_text_node); + } + _current_text_node = _last_text_def->generate(); + _text_render_root->add_child(_current_text_node); + _text_geom_stale = false; + _cursor_stale = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::update_cursor +// Access: Private +// Description: Moves the cursor to its correct position. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +update_cursor() { + qpTextNode *node = get_text_def(get_state()); + nassertv(node != (qpTextNode *)NULL); + + if (_cursor_stale || node != _last_text_def) { + update_text(); + + _cursor_position = min(_cursor_position, (int)_wtext.length()); + + // Determine the row and column of the cursor. + int row = 0; + int column = _cursor_position; + while (row + 1 < (int)_ww_lines.size() && + column > (int)_ww_lines[row]._str.length()) { + column -= _ww_lines[row]._str.length(); + row++; + } + + nassertv(row >= 0 && row < (int)_ww_lines.size()); + nassertv(column >= 0 && column <= (int)_ww_lines[row]._str.length()); + + float width = + _last_text_def->calc_width(_ww_lines[row]._str.substr(0, column)); + float line_height = _last_text_def->get_line_height(); + + LVecBase3f trans(_ww_lines[row]._left + width, 0.0f, -line_height * row); + _cursor_def->set_transform(TransformState::make_pos(trans)); + + _cursor_stale = false; + } + + // Should the cursor be visible? + if (!get_focus()) { + show_hide_cursor(false); + } else { + double elapsed_time = + ClockObject::get_global_clock()->get_frame_time() - _blink_start; + int cycle = (int)(elapsed_time * _blink_rate * 2.0f); + bool visible = ((cycle & 1) == 0); + show_hide_cursor(visible); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::show_hide_cursor +// Access: Private +// Description: Makes the cursor visible or invisible, e.g. during a +// blink cycle. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +show_hide_cursor(bool visible) { + if (visible != _cursor_visible) { + if (visible) { + // Reveal the cursor. + _cursor_def->set_draw_mask(DrawMask::all_on()); + } else { + // Hide the cursor. + _cursor_def->set_draw_mask(DrawMask::all_off()); + } + _cursor_visible = visible; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGEntry::update_state +// Access: Private +// Description: Determines what the correct state for the qpPGEntry +// should be. +//////////////////////////////////////////////////////////////////// +void qpPGEntry:: +update_state() { + if (get_active()) { + if (get_focus()) { + set_state(S_focus); + } else { + set_state(S_no_focus); + } + } else { + set_state(S_inactive); + } +} diff --git a/panda/src/pgui/qppgEntry.h b/panda/src/pgui/qppgEntry.h new file mode 100644 index 0000000000..13d5ca9ccc --- /dev/null +++ b/panda/src/pgui/qppgEntry.h @@ -0,0 +1,194 @@ +// Filename: qppgEntry.h +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef qpPGENTRY_H +#define qpPGENTRY_H + +#include "pandabase.h" + +#include "qppgItem.h" + +#include "qptextNode.h" +#include "pointerTo.h" +#include "pvector.h" +#include "clockObject.h" + +//////////////////////////////////////////////////////////////////// +// Class : qpPGEntry +// Description : This is a particular kind of PGItem that handles +// simple one-line text entries, of the sort where the +// user can type any string. +// +// A qpPGEntry does all of its internal manipulation on a +// wide string, so it can store the full Unicode +// character set. The interface can support either the +// wide string getters and setters, or the normal 8-bit +// string getters and setters, which use whatever +// encoding method is specified by the associated +// TextNode. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA qpPGEntry : public qpPGItem { +PUBLISHED: + qpPGEntry(const string &name); + virtual ~qpPGEntry(); + +protected: + qpPGEntry(const qpPGEntry ©); + +public: + virtual PandaNode *make_copy() const; + virtual bool has_cull_callback() const; + virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data); + + virtual void press(const MouseWatcherParameter ¶m, bool background); + virtual void keystroke(const MouseWatcherParameter ¶m, bool background); + + virtual void accept(const MouseWatcherParameter ¶m); + virtual void overflow(const MouseWatcherParameter ¶m); + virtual void type(const MouseWatcherParameter ¶m); + virtual void erase(const MouseWatcherParameter ¶m); + +PUBLISHED: + enum State { + S_focus = 0, + S_no_focus, + S_inactive + }; + + void setup(float width, int num_lines); + + INLINE void set_text(const string &text); + INLINE string get_text() const; + + INLINE void set_cursor_position(int position); + INLINE int get_cursor_position() const; + + INLINE void set_max_chars(int max_chars); + INLINE int get_max_chars() const; + INLINE void set_max_width(float max_width); + INLINE float get_max_width() const; + INLINE void set_num_lines(int num_lines); + INLINE int get_num_lines() const; + + INLINE void set_blink_rate(float blink_rate); + INLINE float get_blink_rate() const; + + INLINE PandaNode *get_cursor_def(); + INLINE void clear_cursor_def(); + + INLINE void set_cursor_keys_active(bool flag); + INLINE bool get_cursor_keys_active() const; + + INLINE void set_obscure_mode(bool flag); + INLINE bool get_obscure_mode() const; + + void set_text_def(int state, qpTextNode *node); + qpTextNode *get_text_def(int state) const; + + virtual void set_active(bool active); + virtual void set_focus(bool focus); + + INLINE static string get_accept_prefix(); + INLINE static string get_overflow_prefix(); + INLINE static string get_type_prefix(); + INLINE static string get_erase_prefix(); + + INLINE string get_accept_event(const ButtonHandle &button) const; + INLINE string get_overflow_event() const; + INLINE string get_type_event() const; + INLINE string get_erase_event() const; + +public: + INLINE void set_wtext(const wstring &wtext); + INLINE const wstring &get_wtext() const; + + +private: + const wstring &get_display_wtext(); + void slot_text_def(int state); + void update_text(); + void update_cursor(); + void show_hide_cursor(bool visible); + void update_state(); + + wstring _wtext; + wstring _obscured_wtext; + int _cursor_position; + bool _cursor_stale; + bool _cursor_visible; + + int _max_chars; + float _max_width; + int _num_lines; + + typedef pvector< PT(qpTextNode) > TextDefs; + TextDefs _text_defs; + + // This node is the root of the subgraph that renders both the text + // and the cursor. + PT(PandaNode) _text_render_root; + + // This is the node for rendering the actual text that is parented + // to the above node when the text is generated. + PT(PandaNode) _current_text_node; + qpTextNode *_last_text_def; + bool _text_geom_stale; + + // This is a list of each row of text in the entry, after it has + // been wordwrapped by update_text(). It's used by update_cursor() + // to compute the correct cursor position. + class WWLine { + public: + wstring _str; + float _left; + }; + typedef pvector WWLines; + WWLines _ww_lines; + + // This is the node that represents the cursor geometry. It is also + // attached to the above node, and is transformed around and/or + // hidden according to the cursor's position and blink state. + PandaNode *_cursor_def; + + double _blink_start; + double _blink_rate; + + bool _cursor_keys_active; + bool _obscure_mode; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + qpPGItem::init_type(); + register_type(_type_handle, "qpPGEntry", + qpPGItem::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "qppgEntry.I" + +#endif diff --git a/panda/src/pgui/qppgItem.I b/panda/src/pgui/qppgItem.I new file mode 100644 index 0000000000..dcfd631474 --- /dev/null +++ b/panda/src/pgui/qppgItem.I @@ -0,0 +1,468 @@ +// Filename: qppgItem.I +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_region +// Access: Public +// Description: Returns the MouseWatcherRegion associated with this +// item. Every qpPGItem has a MouseWatcherRegion +// associated with it, that is created when the qpPGItem +// is created; it does not change during the lifetime of +// the qpPGItem. Even items that do not have a frame have +// an associated MouseWatcherRegion, although it will +// not be used in this case. +//////////////////////////////////////////////////////////////////// +INLINE PGMouseWatcherRegion *qpPGItem:: +get_region() const { + return _region; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_frame +// Access: Published +// Description: Sets the bounding rectangle of the item, in local +// coordinates. This is the region on screen within +// which the mouse will be considered to be within the +// item. Normally, it should correspond to the bounding +// rectangle of the visible geometry of the item. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +set_frame(float left, float right, float bottom, float top) { + set_frame(LVecBase4f(left, right, bottom, top)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_frame +// Access: Published +// Description: Sets the bounding rectangle of the item, in local +// coordinates. This is the region on screen within +// which the mouse will be considered to be within the +// item. Normally, it should correspond to the bounding +// rectangle of the visible geometry of the item. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +set_frame(const LVecBase4f &frame) { + _has_frame = true; + _frame = frame; + mark_frames_stale(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_frame +// Access: Published +// Description: Returns the bounding rectangle of the item. See +// set_frame(). It is an error to call this if +// has_frame() returns false. +//////////////////////////////////////////////////////////////////// +INLINE const LVecBase4f &qpPGItem:: +get_frame() const { + nassertr(has_frame(), _frame); + return _frame; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::has_frame +// Access: Published +// Description: Returns true if the item has a bounding rectangle; +// see set_frame(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGItem:: +has_frame() const { + return _has_frame; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::clear_frame +// Access: Published +// Description: Removes the bounding rectangle from the item. It +// will no longer be possible to position the mouse +// within the item; see set_frame(). +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +clear_frame() { + _has_frame = false; + mark_frames_stale(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_state +// Access: Published +// Description: Sets the "state" of this particular qpPGItem. +// +// The qpPGItem node will render as if it were the +// subgraph assigned to the corresponding index via +// set_state_def(). +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +set_state(int state) { + _state = state; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_state +// Access: Published +// Description: Returns the "state" of this particular qpPGItem. See +// set_state(). +//////////////////////////////////////////////////////////////////// +INLINE int qpPGItem:: +get_state() const { + return _state; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_active +// Access: Published +// Description: Returns whether the qpPGItem is currently active for +// mouse events. See set_active(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGItem:: +get_active() const { + return (_flags & F_active) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_focus +// Access: Published +// Description: Returns whether the qpPGItem currently has focus for +// keyboard events. See set_focus(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGItem:: +get_focus() const { + return (_flags & F_focus) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_background_focus +// Access: Published +// Description: Returns whether background_focus is currently +// enabled. See set_background_focus(). +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGItem:: +get_background_focus() const { + return (_flags & F_background_focus) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_suppress_flags +// Access: Published +// Description: This is just an interface to set the suppress flags +// on the underlying MouseWatcherRegion. See +// MouseWatcherRegion::set_suppress_flags(). +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +set_suppress_flags(int suppress_flags) { + _region->set_suppress_flags(suppress_flags); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_suppress_flags +// Access: Published +// Description: This is just an interface to get the suppress flags +// on the underlying MouseWatcherRegion. See +// MouseWatcherRegion::get_suppress_flags(). +//////////////////////////////////////////////////////////////////// +INLINE int qpPGItem:: +get_suppress_flags() const { + return _region->get_suppress_flags(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_id +// Access: Published +// Description: Returns the unique ID assigned to this qpPGItem. This +// will be assigned to the region created with the +// MouseWatcher, and will thus be used to generate event +// names. +//////////////////////////////////////////////////////////////////// +INLINE const string &qpPGItem:: +get_id() const { + return _region->get_name(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_id +// Access: Published +// Description: Set the unique ID assigned to this qpPGItem. It is the +// user's responsibility to ensure that this ID is +// unique. +// +// Normally, this should not need to be called, as the +// qpPGItem will assign itself an ID when it is created, +// but this function allows the user to decide to +// redefine the ID to be something possibly more +// meaningful. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +set_id(const string &id) { + _region->set_name(id); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_enter_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the enter +// event for all qpPGItems. The enter event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_enter_prefix() { + return "enter-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_exit_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the exit +// event for all qpPGItems. The exit event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_exit_prefix() { + return "exit-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_within_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the within +// event for all qpPGItems. The within event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_within_prefix() { + return "within-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_without_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the without +// event for all qpPGItems. The without event is the +// concatenation of this string followed by get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_without_prefix() { + return "without-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_focus_in_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the focus_in +// event for all qpPGItems. The focus_in event is the +// concatenation of this string followed by get_id(). +// +// Unlike most item events, this event is thrown with no +// parameters. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_focus_in_prefix() { + return "fin-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_focus_out_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the focus_out +// event for all qpPGItems. The focus_out event is the +// concatenation of this string followed by get_id(). +// +// Unlike most item events, this event is thrown with no +// parameters. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_focus_out_prefix() { + return "fout-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_press_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the press +// event for all qpPGItems. The press event is the +// concatenation of this string followed by a button +// name, followed by a hyphen and get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_press_prefix() { + return "press-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_release_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the release +// event for all qpPGItems. The release event is the +// concatenation of this string followed by a button +// name, followed by a hyphen and get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_release_prefix() { + return "release-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_keystroke_prefix +// Access: Published, Static +// Description: Returns the prefix that is used to define the +// keystroke event for all qpPGItems. The keystroke event +// is the concatenation of this string followed by a +// hyphen and get_id(). +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_keystroke_prefix() { + return "keystroke-"; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_enter_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and the mouse enters its frame, but +// not any nested frames. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_enter_event() const { + return get_enter_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_exit_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and the mouse exits its frame, or +// enters a nested frame. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_exit_event() const { + return get_exit_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_within_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and the mouse moves within the +// boundaries of the frame. This is different from the +// enter_event in that the mouse is considered within +// the frame even if it is also within a nested frame. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_within_event() const { + return get_within_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_without_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and the mouse moves completely outside +// the boundaries of the frame. This is different from +// the exit_event in that the mouse is considered +// within the frame even if it is also within a nested +// frame. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_without_event() const { + return get_without_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_focus_in_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item gets the keyboard focus. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_focus_in_event() const { + return get_focus_in_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_focus_out_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item loses the keyboard focus. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_focus_out_event() const { + return get_focus_out_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_press_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and the indicated mouse or keyboard +// button is depressed while the mouse is within the +// frame. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_press_event(const ButtonHandle &button) const { + return get_press_prefix() + button.get_name() + "-" + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_release_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and the indicated mouse or keyboard +// button, formerly clicked down is within the frame, is +// released. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_release_event(const ButtonHandle &button) const { + return get_release_prefix() + button.get_name() + "-" + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_keystroke_event +// Access: Published +// Description: Returns the event name that will be thrown when the +// item is active and any key is pressed by the user. +//////////////////////////////////////////////////////////////////// +INLINE string qpPGItem:: +get_keystroke_event() const { + return get_keystroke_prefix() + get_id(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_text_node +// Access: Published, Static +// Description: Changes the TextNode object that will be used by all +// qpPGItems to generate default labels given a string. +// This can be loaded with the default font, etc. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGItem:: +set_text_node(qpTextNode *node) { + _text_node = node; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_focus_item +// Access: Published, Static +// Description: Returns the one qpPGItem in the world that currently +// has keyboard focus, if any, or NULL if no item has +// keyboard focus. Use qpPGItem::set_focus() to activate +// or deactivate keyboard focus on a particular item. +//////////////////////////////////////////////////////////////////// +INLINE qpPGItem *qpPGItem:: +get_focus_item() { + return _focus_item; +} diff --git a/panda/src/pgui/qppgItem.cxx b/panda/src/pgui/qppgItem.cxx new file mode 100644 index 0000000000..3e4497b9f4 --- /dev/null +++ b/panda/src/pgui/qppgItem.cxx @@ -0,0 +1,849 @@ +// Filename: qppgItem.cxx +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "qppgItem.h" +#include "pgTop.h" +#include "pgMouseWatcherParameter.h" +#include "pgCullTraverser.h" +#include "config_pgui.h" + +#include "pandaNode.h" +#include "throw_event.h" +#include "string_utils.h" +#include "qpnodePath.h" +#include "qpcullTraverser.h" + +#ifdef HAVE_AUDIO +#include "audioSound.h" +#endif + +TypeHandle qpPGItem::_type_handle; +PT(qpTextNode) qpPGItem::_text_node; +qpPGItem *qpPGItem::_focus_item = (qpPGItem *)NULL; +qpPGItem::BackgroundFocus qpPGItem::_background_focus; + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpPGItem:: +qpPGItem(const string &name) : + PandaNode(name) +{ + _has_frame = false; + _frame.set(0, 0, 0, 0); + _region = new PGMouseWatcherRegion(this); + _state = 0; + _flags = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +qpPGItem:: +~qpPGItem() { + nassertv(_region->_qpitem == this); + _region->_qpitem = (qpPGItem *)NULL; + + set_background_focus(false); + if (_focus_item == this) { + _focus_item = (qpPGItem *)NULL; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +qpPGItem:: +qpPGItem(const qpPGItem ©) : + PandaNode(copy), + _has_frame(copy._has_frame), + _frame(copy._frame), + _state(copy._state), + _flags(copy._flags) +{ + _region = new PGMouseWatcherRegion(this); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 *qpPGItem:: +make_copy() const { + return new qpPGItem(*this); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +xform(const LMatrix4f &mat) { + // Transform the frame. + LPoint3f ll(_frame[0], 0.0, _frame[2]); + LPoint3f ur(_frame[1], 0.0, _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) { + PandaNode *node = (*di)._node; + if (node != (PandaNode *)NULL) { + // Apply the matrix to the previous transform. + node->set_transform(node->get_transform()->compose(TransformState::make_mat(mat))); + + /* + // Now flatten the transform into the subgraph. + SceneGraphReducer gr; + gr.apply_transitions(arc); + */ + } + + // Transform the frame style too. + if ((*di)._frame_style.xform(mat)) { + (*di)._frame_stale = true; + } + } + mark_bound_stale(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +has_cull_callback() const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +cull_callback(qpCullTraverser *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(); + activate_region(transform, pg_trav->_sort_index); + pg_trav->_sort_index++; + + 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. + PandaNode *def = get_state_def(get_state()); + trav->traverse(def, data); + } + + // Now continue to render everything else below this node. + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 *qpPGItem:: +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++) { + PandaNode *node = get_state_def(i); + if (node != (PandaNode *)NULL) { + child_volumes.push_back(&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: qpPGItem::activate_region +// Access: Public +// Description: Applies the indicated scene graph transform and order +// as determined by the traversal from PGTop. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +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.0, _frame[2]); + LPoint3f lr(_frame[1], 0.0, _frame[2]); + LPoint3f ul(_frame[0], 0.0, _frame[3]); + LPoint3f ur(_frame[1], 0.0, _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); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +enter(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_enter_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +exit(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_exit_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +within(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_within_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +without(const MouseWatcherParameter ¶m) { + PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param); + string event = get_without_event(); + play_sound(event); + throw_event(event, EventParameter(ep)); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::focus_in +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// widget gets the keyboard focus. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +focus_in() { + string event = get_focus_in_event(); + play_sound(event); + throw_event(event); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::focus_out +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever the +// widget loses the keyboard focus. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +focus_out() { + string event = get_focus_out_event(); + play_sound(event); + throw_event(event); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +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)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +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)); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::keystroke +// Access: Public, Virtual +// Description: This is a callback hook function, called whenever +// the user presses a key. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +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: qpPGItem::background_press +// Access: Public, Static +// Description: Calls press() on all the qpPGItems with background +// focus. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +background_press(const MouseWatcherParameter ¶m) { + BackgroundFocus::const_iterator fi; + for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { + qpPGItem *item = *fi; + if (!item->get_focus()) { + item->press(param, true); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::background_release +// Access: Public, Static +// Description: Calls release() on all the qpPGItems with background +// focus. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +background_release(const MouseWatcherParameter ¶m) { + BackgroundFocus::const_iterator fi; + for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { + qpPGItem *item = *fi; + if (!item->get_focus()) { + item->release(param, true); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::background_keystroke +// Access: Public, Static +// Description: Calls keystroke() on all the qpPGItems with background +// focus. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +background_keystroke(const MouseWatcherParameter ¶m) { + BackgroundFocus::const_iterator fi; + for (fi = _background_focus.begin(); fi != _background_focus.end(); ++fi) { + qpPGItem *item = *fi; + if (!item->get_focus()) { + item->keystroke(param, true); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_active +// Access: Published, Virtual +// Description: Sets whether the qpPGItem 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 qpPGItem:: +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: qpPGItem::set_focus +// Access: Published, Virtual +// Description: Sets whether the qpPGItem 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 qpPGItem 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 qpPGItem:: +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 != (qpPGItem *)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 = (qpPGItem *)NULL; + } + + if (get_focus()) { + focus_out(); + _flags &= ~F_focus; + } + } + _region->set_keyboard(focus); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +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: qpPGItem::get_num_state_defs +// Access: Published +// Description: Returns one more than the highest-numbered state def +// that was ever assigned to the qpPGItem. 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 qpPGItem:: +get_num_state_defs() const { + return _state_defs.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +has_state_def(int state) const { + if (state < 0 || state >= (int)_state_defs.size()) { + return false; + } + return (_state_defs[state]._node != (PandaNode *)NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +clear_state_def(int state) { + if (state < 0 || state >= (int)_state_defs.size()) { + return; + } + + PandaNode *node = _state_defs[state]._node; + if (node != (PandaNode *)NULL) { + node->remove_all_children(); + } + + _state_defs[state]._frame_node = (PandaNode *)NULL; + _state_defs[state]._frame_stale = true; + + mark_bound_stale(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_state_def +// Access: Published +// Description: Returns the Node that is the root of the subgraph +// that will be drawn when the qpPGItem is in the +// indicated state. The first time this is called for a +// particular state index, it may create the Node. +//////////////////////////////////////////////////////////////////// +PandaNode *qpPGItem:: +get_state_def(int state) { + nassertr(state >= 0 && state < 1000, (PandaNode *)NULL); // Sanity check. + slot_state_def(state); + + if (_state_defs[state]._node == (PandaNode *)NULL) { + // Create a new node. + _state_defs[state]._node = new PandaNode("state_" + format_string(state)); + _state_defs[state]._frame_stale = true; + } + + if (_state_defs[state]._frame_stale) { + update_frame(state); + } + + return _state_defs[state]._node; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::instance_to_state_def +// Access: Published +// Description: Parents an instance of the bottom node of the +// indicated NodePath to the indicated state index. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +instance_to_state_def(int state, const qpNodePath &path) { + if (path.is_empty()) { + // If the path is empty, quietly do nothing. + return; + } + + get_state_def(state)->add_child(path.node()); + + mark_bound_stale(); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +get_frame_style(int state) { + if (state < 0 || state >= (int)_state_defs.size()) { + return PGFrameStyle(); + } + return _state_defs[state]._frame_style; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +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. + PandaNode *def = get_state_def(state); + nassertv(def != (PandaNode *)NULL); + + _state_defs[state]._frame_style = style; + _state_defs[state]._frame_stale = true; + + mark_bound_stale(); +} + +#ifdef HAVE_AUDIO +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::set_sound +// Access: Published +// Description: Sets the sound that will be played whenever the +// indicated event occurs. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +set_sound(const string &event, AudioSound *sound) { + _sounds[event] = sound; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::clear_sound +// Access: Published +// Description: Removes the sound associated with the indicated +// event. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +clear_sound(const string &event) { + _sounds.erase(event); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_sound +// Access: Published +// Description: Returns the sound associated with the indicated +// event, or NULL if there is no associated sound. +//////////////////////////////////////////////////////////////////// +AudioSound *qpPGItem:: +get_sound(const string &event) const { + Sounds::const_iterator si = _sounds.find(event); + if (si != _sounds.end()) { + return (*si).second; + } + return (AudioSound *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::has_sound +// Access: Published +// Description: Returns true if there is a sound associated with the +// indicated event, or false otherwise. +//////////////////////////////////////////////////////////////////// +bool qpPGItem:: +has_sound(const string &event) const { + return (_sounds.count(event) != 0); +} +#endif // HAVE_AUDIO + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::get_text_node +// Access: Published, Static +// Description: Returns the TextNode object that will be used by all +// qpPGItems to generate default labels given a string. +// This can be loaded with the default font, etc. +//////////////////////////////////////////////////////////////////// +qpTextNode *qpPGItem:: +get_text_node() { + if (_text_node == (qpTextNode *)NULL) { + _text_node = new qpTextNode("pguiText"); + _text_node->freeze(); + _text_node->set_text_color(0.0, 0.0, 0.0, 1.0); + + // The default TextNode is aligned to the left, for the + // convenience of PGEntry. + _text_node->set_align(qpTextNode::A_left); + } + return _text_node; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::play_sound +// Access: Protected +// Description: Plays the sound associated with the indicated event, +// if there is one. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +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: qpPGItem::slot_state_def +// Access: Private +// Description: Ensures there is a slot in the array for the given +// state definition. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +slot_state_def(int state) { + while (state >= (int)_state_defs.size()) { + StateDef def; + def._frame_stale = true; + _state_defs.push_back(def); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::update_frame +// Access: Private +// Description: Generates a new instance of the frame geometry for +// the indicated state. +//////////////////////////////////////////////////////////////////// +void qpPGItem:: +update_frame(int state) { + // First, remove the old frame geometry, if any. + if (state >= 0 && state < (int)_state_defs.size()) { + PandaNode *old_frame = _state_defs[state]._frame_node; + if (old_frame != (PandaNode *)NULL) { + PandaNode *node = _state_defs[state]._node; + nassertv(node != (PandaNode *)NULL); + node->remove_child(old_frame); + _state_defs[state]._frame_node = (PandaNode *)NULL; + } + } + + // 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()) { + PandaNode *node = get_state_def(state); + nassertv(node != (PandaNode *)NULL); + _state_defs[state]._frame_node = + _state_defs[state]._frame_style.generate_into(node, _frame); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGItem::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 qpPGItem:: +mark_frames_stale() { + StateDefs::iterator di; + for (di = _state_defs.begin(); di != _state_defs.end(); ++di) { + // Remove the old frame, if any. + PandaNode *old_frame = (*di)._frame_node; + if (old_frame != (PandaNode *)NULL) { + PandaNode *node = (*di)._node; + nassertv(node != (PandaNode *)NULL); + node->remove_child(old_frame); + (*di)._frame_node = (PandaNode *)NULL; + } + + (*di)._frame_stale = true; + } + mark_bound_stale(); +} diff --git a/panda/src/pgui/qppgItem.h b/panda/src/pgui/qppgItem.h new file mode 100644 index 0000000000..aec8d49384 --- /dev/null +++ b/panda/src/pgui/qppgItem.h @@ -0,0 +1,213 @@ +// Filename: qppgItem.h +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef qpPGITEM_H +#define qpPGITEM_H + +#include "pandabase.h" + +#include "pgMouseWatcherRegion.h" +#include "pgFrameStyle.h" + +#include "pandaNode.h" +#include "luse.h" +#include "pointerTo.h" +#include "qptextNode.h" + +#include "pmap.h" + +class PGTop; +class MouseWatcherParameter; +class AudioSound; +class qpNodePath; + +//////////////////////////////////////////////////////////////////// +// Class : qpPGItem +// Description : This is the base class for all the various kinds of +// gui widget objects. +// +// It is a Node which corresponds to a rectangular +// region on the screen, and it may have any number of +// "state" subgraphs, one of which is rendered at any +// given time according to its current state. +// +// The qpPGItem node must be parented to the scene graph +// somewhere beneath a PGTop node in order for this +// behavior to work. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA qpPGItem : public PandaNode { +PUBLISHED: + qpPGItem(const string &name); + virtual ~qpPGItem(); + +protected: + qpPGItem(const qpPGItem ©); + + virtual PandaNode *make_copy() const; + virtual void xform(const LMatrix4f &mat); + virtual bool has_cull_callback() const; + virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data); + + virtual BoundingVolume *recompute_internal_bound(); + +public: + void activate_region(const LMatrix4f &transform, int sort); + INLINE PGMouseWatcherRegion *get_region() const; + + virtual void enter(const MouseWatcherParameter ¶m); + virtual void exit(const MouseWatcherParameter ¶m); + virtual void within(const MouseWatcherParameter ¶m); + virtual void without(const MouseWatcherParameter ¶m); + virtual void focus_in(); + virtual void focus_out(); + virtual void press(const MouseWatcherParameter ¶m, bool background); + virtual void release(const MouseWatcherParameter ¶m, bool background); + virtual void keystroke(const MouseWatcherParameter ¶m, bool background); + + static void background_press(const MouseWatcherParameter ¶m); + static void background_release(const MouseWatcherParameter ¶m); + static void background_keystroke(const MouseWatcherParameter ¶m); + +PUBLISHED: + INLINE void set_frame(float left, float right, float bottom, float top); + INLINE void set_frame(const LVecBase4f &frame); + INLINE const LVecBase4f &get_frame() const; + INLINE bool has_frame() const; + INLINE void clear_frame(); + + INLINE void set_state(int state); + INLINE int get_state() const; + + virtual void set_active(bool active); + INLINE bool get_active() const; + + virtual void set_focus(bool focus); + INLINE bool get_focus() const; + + void set_background_focus(bool focus); + INLINE bool get_background_focus() const; + + INLINE void set_suppress_flags(int suppress_flags); + INLINE int get_suppress_flags() const; + + int get_num_state_defs() const; + void clear_state_def(int state); + bool has_state_def(int state) const; + PandaNode *get_state_def(int state); + void instance_to_state_def(int state, const qpNodePath &chain); + + PGFrameStyle get_frame_style(int state); + void set_frame_style(int state, const PGFrameStyle &style); + + INLINE const string &get_id() const; + INLINE void set_id(const string &id); + + INLINE static string get_enter_prefix(); + INLINE static string get_exit_prefix(); + INLINE static string get_within_prefix(); + INLINE static string get_without_prefix(); + INLINE static string get_focus_in_prefix(); + INLINE static string get_focus_out_prefix(); + INLINE static string get_press_prefix(); + INLINE static string get_release_prefix(); + INLINE static string get_keystroke_prefix(); + + INLINE string get_enter_event() const; + INLINE string get_exit_event() const; + INLINE string get_within_event() const; + INLINE string get_without_event() const; + INLINE string get_focus_in_event() const; + INLINE string get_focus_out_event() const; + INLINE string get_press_event(const ButtonHandle &button) const; + INLINE string get_release_event(const ButtonHandle &button) const; + INLINE string get_keystroke_event() const; + +#ifdef HAVE_AUDIO + void set_sound(const string &event, AudioSound *sound); + void clear_sound(const string &event); + AudioSound *get_sound(const string &event) const; + bool has_sound(const string &event) const; +#endif + + static qpTextNode *get_text_node(); + INLINE static void set_text_node(qpTextNode *node); + + INLINE static qpPGItem *get_focus_item(); + +protected: + void play_sound(const string &event); + +private: + void slot_state_def(int state); + void update_frame(int state); + void mark_frames_stale(); + + bool _has_frame; + LVecBase4f _frame; + int _state; + enum Flags { + F_active = 0x01, + F_focus = 0x02, + F_background_focus = 0x04, + }; + int _flags; + + PT(PGMouseWatcherRegion) _region; + + class StateDef { + public: + PT(PandaNode) _node; + PGFrameStyle _frame_style; + PT(PandaNode) _frame_node; + bool _frame_stale; + }; + typedef pvector StateDefs; + StateDefs _state_defs; + +#ifdef HAVE_AUDIO + typedef pmap Sounds; + Sounds _sounds; +#endif + + static PT(qpTextNode) _text_node; + static qpPGItem *_focus_item; + + typedef pset BackgroundFocus; + static BackgroundFocus _background_focus; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + PandaNode::init_type(); + register_type(_type_handle, "qpPGItem", + PandaNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "qppgItem.I" + +#endif diff --git a/panda/src/pgui/qppgTop.I b/panda/src/pgui/qppgTop.I new file mode 100644 index 0000000000..47bf8a92f8 --- /dev/null +++ b/panda/src/pgui/qppgTop.I @@ -0,0 +1,70 @@ +// Filename: qppgTop.I +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::Copy Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +INLINE qpPGTop:: +qpPGTop(const qpPGTop ©) : + PandaNode(copy), + _watcher(copy._watcher) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::get_mouse_watcher +// Access: Published +// Description: Returns the MouseWatcher pointer that the qpPGTop object +// registers its PG items with, or NULL if the +// MouseWatcher has not yet been set. +//////////////////////////////////////////////////////////////////// +INLINE qpMouseWatcher *qpPGTop:: +get_mouse_watcher() const { + return _watcher; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::add_region +// Access: Public +// Description: Adds the indicated region to the set of regions in +// the group. Returns true if it was successfully +// added, or false if it was already on the list. +//////////////////////////////////////////////////////////////////// +INLINE bool qpPGTop:: +add_region(MouseWatcherRegion *region) { + if (_watcher_group == (PGMouseWatcherGroup *)NULL) { + return false; + } + return _watcher_group->add_region(region); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::clear_regions +// Access: Public +// Description: Removes all the regions from the group. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGTop:: +clear_regions() { + if (_watcher_group == (PGMouseWatcherGroup *)NULL) { + return; + } + _watcher_group->clear_regions(); +} diff --git a/panda/src/pgui/qppgTop.cxx b/panda/src/pgui/qppgTop.cxx new file mode 100644 index 0000000000..7316f91384 --- /dev/null +++ b/panda/src/pgui/qppgTop.cxx @@ -0,0 +1,173 @@ +// Filename: qppgTop.cxx +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "qppgTop.h" +#include "pgItem.h" +#include "pgMouseWatcherGroup.h" +#include "pgCullTraverser.h" + +#include "omniBoundingVolume.h" + +TypeHandle qpPGTop::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpPGTop:: +qpPGTop(const string &name) : + PandaNode(name) +{ + _watcher_group = (PGMouseWatcherGroup *)NULL; + + // A qpPGTop node normally has an infinite bounding volume. Screw + // culling. + set_bound(OmniBoundingVolume()); + set_final(true); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +qpPGTop:: +~qpPGTop() { + set_mouse_watcher((qpMouseWatcher *)NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::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 *qpPGTop:: +make_copy() const { + return new qpPGTop(*this); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::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 qpPGTop:: +has_cull_callback() const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::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 qpPGTop:: +cull_callback(qpCullTraverser *trav, CullTraverserData &data) { + // Empty our set of regions in preparation for re-adding whichever + // ones we encounter in the traversal that are current. + clear_regions(); + + // Now subsitute for the normal CullTraverser a special one of our + // own choosing. This just carries around a pointer back to the + // PGTop node, for the convenience of PGItems to register themselves + // as they are drawn. + PGCullTraverser pg_trav(this, trav); + pg_trav.traverse_below(this, data); + + // We've taken care of the traversal, thank you. + return false; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGTop::set_mouse_watcher +// Access: Published +// Description: Sets the MouseWatcher pointer that the qpPGTop object +// registers its PG items with. This must be set before +// the PG items are active. +//////////////////////////////////////////////////////////////////// +void qpPGTop:: +set_mouse_watcher(qpMouseWatcher *watcher) { + if (_watcher_group != (PGMouseWatcherGroup *)NULL) { + _watcher_group->clear_top(this); + } + if (_watcher != (qpMouseWatcher *)NULL) { + _watcher->remove_group(_watcher_group); + } + + _watcher = watcher; + _watcher_group = (PGMouseWatcherGroup *)NULL; + + if (_watcher != (qpMouseWatcher *)NULL) { + // We create a new PGMouseWatcherGroup, but we don't own the + // reference count; the watcher will own this for us. + _watcher_group = new PGMouseWatcherGroup(this); + _watcher->add_group(_watcher_group); + } +} + +/* + if (node->is_of_type(PGItem::get_class_type())) { + PGItem *pgi = DCAST(PGItem, node); + + if (pgi->has_frame() && pgi->get_active()) { + // The item has a frame, so we want to generate a region for it + // and update the MouseWatcher. + + // Get the complete net transform to the PGItem from the top. + LMatrix4f mat; + + NodeTransitionWrapper ntw(TransformTransition::get_class_type()); + wrt(pgi, chain.begin(), chain.end(), (Node *)NULL, + ntw, RenderRelation::get_class_type()); + const TransformTransition *tt; + if (!get_transition_into(tt, ntw)) { + // No relative transform. + mat = LMatrix4f::ident_mat(); + } else { + mat = tt->get_matrix(); + } + + // Now apply this transform to the item's frame. + pgi->activate_region(this, mat, _sort_index); + _sort_index++; + + add_region(pgi->get_region()); + } +*/ diff --git a/panda/src/pgui/qppgTop.h b/panda/src/pgui/qppgTop.h new file mode 100644 index 0000000000..d1acfb9a2d --- /dev/null +++ b/panda/src/pgui/qppgTop.h @@ -0,0 +1,95 @@ +// Filename: qppgTop.h +// Created by: drose (13Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef qpPGTOP_H +#define qpPGTOP_H + +#include "pandabase.h" + +#include "pgMouseWatcherGroup.h" + +#include "pandaNode.h" +#include "qpmouseWatcher.h" +#include "pointerTo.h" + +class GraphicsStateGuardian; +class PGMouseWatcherGroup; + +//////////////////////////////////////////////////////////////////// +// Class : PGTop +// Description : The "top" node of the new Panda GUI system. This +// node must be parented to the 2-d scene graph, and all +// PG objects should be parented to this node or +// somewhere below it. PG objects not parented within +// this hierarchy will not be clickable. +// +// This node begins the special traversal of the PG +// objects that registers each node within the +// MouseWatcher and forces everything to render in a +// depth-first, left-to-right order, appropriate for 2-d +// objects. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA qpPGTop : public PandaNode { +PUBLISHED: + qpPGTop(const string &name); + virtual ~qpPGTop(); + +protected: + INLINE qpPGTop(const qpPGTop ©); + +public: + virtual PandaNode *make_copy() const; + virtual bool has_cull_callback() const; + virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data); + +PUBLISHED: + void set_mouse_watcher(qpMouseWatcher *watcher); + INLINE qpMouseWatcher *get_mouse_watcher() const; + +public: + // These methods duplicate the functionality of MouseWatcherGroup. + INLINE bool add_region(MouseWatcherRegion *region); + INLINE void clear_regions(); + +private: + PT(qpMouseWatcher) _watcher; + PGMouseWatcherGroup *_watcher_group; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + PandaNode::init_type(); + register_type(_type_handle, "qpPGTop", + PandaNode::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class PGMouseWatcherGroup; +}; + +#include "qppgTop.I" + +#endif diff --git a/panda/src/pgui/qppgWaitBar.I b/panda/src/pgui/qppgWaitBar.I new file mode 100644 index 0000000000..0f35e81025 --- /dev/null +++ b/panda/src/pgui/qppgWaitBar.I @@ -0,0 +1,94 @@ +// Filename: qppgWaitBar.I +// Created by: drose (14Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::set_range +// Access: Published +// Description: Sets the value at which the WaitBar indicates 100%. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGWaitBar:: +set_range(float range) { + _range = range; + _bar_state = -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::get_range +// Access: Published +// Description: Returns the value at which the WaitBar indicates 100%. +//////////////////////////////////////////////////////////////////// +INLINE float qpPGWaitBar:: +get_range() const { + return _range; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::set_value +// Access: Published +// Description: Sets the current value of the bar. This should range +// between 0 and get_range(). +//////////////////////////////////////////////////////////////////// +INLINE void qpPGWaitBar:: +set_value(float value) { + _value = value; + _bar_state = -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::get_value +// Access: Published +// Description: Returns the current value of the bar. +//////////////////////////////////////////////////////////////////// +INLINE float qpPGWaitBar:: +get_value() const { + return _value; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::get_percent +// Access: Published +// Description: Returns the percentage complete. +//////////////////////////////////////////////////////////////////// +INLINE float qpPGWaitBar:: +get_percent() const { + return _value / _range * 100.0; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::set_bar_style +// Access: Published +// Description: Sets the kind of frame that is drawn on top of the +// WaitBar to represent the amount completed. +//////////////////////////////////////////////////////////////////// +INLINE void qpPGWaitBar:: +set_bar_style(const PGFrameStyle &style) { + _bar_style = style; + _bar_state = -1; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::get_bar_style +// Access: Published +// Description: Returns the kind of frame that is drawn on top of the +// WaitBar to represent the amount completed. +//////////////////////////////////////////////////////////////////// +INLINE PGFrameStyle qpPGWaitBar:: +get_bar_style() const { + return _bar_style; +} diff --git a/panda/src/pgui/qppgWaitBar.cxx b/panda/src/pgui/qppgWaitBar.cxx new file mode 100644 index 0000000000..4d792ffb88 --- /dev/null +++ b/panda/src/pgui/qppgWaitBar.cxx @@ -0,0 +1,189 @@ +// Filename: qppgWaitBar.cxx +// Created by: drose (14Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#include "qppgWaitBar.h" +#include "pgMouseWatcherParameter.h" + +#include "throw_event.h" + +TypeHandle qpPGWaitBar::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +qpPGWaitBar:: +qpPGWaitBar(const string &name) : qpPGItem(name) +{ + _range = 100.0; + _value = 0.0; + _bar_state = -1; + _bar_node = (PandaNode *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +qpPGWaitBar:: +~qpPGWaitBar() { +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::Copy Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +qpPGWaitBar:: +qpPGWaitBar(const qpPGWaitBar ©) : + qpPGItem(copy), + _range(copy._range), + _value(copy._value) +{ + _bar_state = -1; + _bar_node = (PandaNode *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::make_copy +// Access: Public, 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 *qpPGWaitBar:: +make_copy() const { + return new qpPGWaitBar(*this); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::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 qpPGWaitBar:: +has_cull_callback() const { + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::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 qpPGWaitBar:: +cull_callback(qpCullTraverser *trav, CullTraverserData &data) { + update(); + return qpPGItem::cull_callback(trav, data); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::setup +// Access: Public +// Description: Creates a qpPGWaitBar with the indicated dimensions, +// with the indicated maximum range. +//////////////////////////////////////////////////////////////////// +void qpPGWaitBar:: +setup(float width, float height, float range) { + set_state(0); + clear_state_def(0); + + set_frame(-0.5 * width, 0.5 * width, -0.5 * height, 0.5 * height); + + PGFrameStyle style; + style.set_width(0.05, 0.05); + + style.set_color(0.6, 0.6, 0.6, 1.0); + style.set_type(PGFrameStyle::T_bevel_in); + set_frame_style(0, style); + + style.set_color(0.8, 0.8, 0.8, 1.0); + style.set_type(PGFrameStyle::T_bevel_out); + set_bar_style(style); +} + +//////////////////////////////////////////////////////////////////// +// Function: qpPGWaitBar::update +// Access: Private +// Description: Computes the appropriate size of the bar frame +// according to the percentage completed. +//////////////////////////////////////////////////////////////////// +void qpPGWaitBar:: +update() { + int state = get_state(); + + // If the bar was last drawn in this state and is still current, we + // don't have to draw it again. + if (_bar_state == state) { + return; + } + + // Remove the old bar geometry, if any. + if (_bar_node != (PandaNode *)NULL) { + PandaNode *parent_node = _bar_node->get_parent(0); + parent_node->remove_child(_bar_node); + _bar_node = (PandaNode *)NULL; + } + + // Now create new bar geometry. + if (_value != 0.0 && _range != 0.0) { + PandaNode *node = get_state_def(state); + nassertv(node != (PandaNode *)NULL); + + PGFrameStyle style = get_frame_style(state); + const LVecBase4f &frame = get_frame(); + const LVecBase2f &width = style.get_width(); + + // Put the bar within the item's frame's border. + LVecBase4f bar_frame(frame[0] + width[0], + frame[1] - width[0], + frame[2] + width[1], + frame[3] - width[1]); + + // And scale the bar according to our value. + float frac = _value / _range; + frac = max(min(frac, 1.0f), 0.0f); + bar_frame[1] = bar_frame[0] + frac * (bar_frame[1] - bar_frame[0]); + + _bar_node = _bar_style.generate_into(node, bar_frame); + } + + // Indicate that the bar is current for this state. + _bar_state = state; +} diff --git a/panda/src/pgui/qppgWaitBar.h b/panda/src/pgui/qppgWaitBar.h new file mode 100644 index 0000000000..55bb9cb6e4 --- /dev/null +++ b/panda/src/pgui/qppgWaitBar.h @@ -0,0 +1,88 @@ +// Filename: qppgWaitBar.h +// Created by: drose (14Mar02) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001, 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://www.panda3d.org/license.txt . +// +// To contact the maintainers of this program write to +// panda3d@yahoogroups.com . +// +//////////////////////////////////////////////////////////////////// + +#ifndef qpPGWAITBAR_H +#define qpPGWAITBAR_H + +#include "pandabase.h" + +#include "qppgItem.h" + +//////////////////////////////////////////////////////////////////// +// Class : qpPGWaitBar +// Description : This is a particular kind of qpPGItem that draws a +// little bar that fills from left to right to indicate +// a slow process gradually completing, like a +// traditional "wait, loading" bar. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA qpPGWaitBar : public qpPGItem { +PUBLISHED: + qpPGWaitBar(const string &name = ""); + virtual ~qpPGWaitBar(); + +protected: + qpPGWaitBar(const qpPGWaitBar ©); + +public: + virtual PandaNode *make_copy() const; + virtual bool has_cull_callback() const; + virtual bool cull_callback(qpCullTraverser *trav, CullTraverserData &data); + +PUBLISHED: + void setup(float width, float height, float range); + + INLINE void set_range(float range); + INLINE float get_range() const; + + INLINE void set_value(float value); + INLINE float get_value() const; + + INLINE float get_percent() const; + + INLINE void set_bar_style(const PGFrameStyle &style); + INLINE PGFrameStyle get_bar_style() const; + +private: + void update(); + + float _range, _value; + int _bar_state; + PGFrameStyle _bar_style; + PT(PandaNode) _bar_node; + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + qpPGItem::init_type(); + register_type(_type_handle, "qpPGWaitBar", + qpPGItem::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; +}; + +#include "qppgWaitBar.I" + +#endif