📄 gameswf_button.cpp
字号:
// gameswf_button.cpp -- Thatcher Ulrich <tu@tulrich.com> 2003// This source code has been donated to the Public Domain. Do// whatever you want with it.// SWF buttons. Mouse-sensitive update/display, actions, etc.#include "gameswf_button.h"#include "gameswf_action.h"#include "gameswf_render.h"#include "gameswf_sound.h"#include "gameswf_stream.h"/*Observations about button & mouse behaviorEntities that receive mouse events: only buttons and sprites, AFAIKWhen the mouse button goes down, it becomes "captured" by whateverelement is topmost, directly below the mouse at that moment. Whilethe mouse is captured, no other entity receives mouse events,regardless of how the mouse or other elements move.The mouse remains captured until the mouse button goes up. The mouseremains captured even if the element that captured it is removed fromthe display list.If the mouse isn't above a button or sprite when the mouse button goesdown, then the mouse is captured by the background (i.e. mouse eventsjust don't get sent, until the mouse button goes up again).Mouse events:+------------------+---------------+-------------------------------------+| Event | Mouse Button | description |=========================================================================| onRollOver | up | sent to topmost entity when mouse || | | cursor initially goes over it |+------------------+---------------+-------------------------------------+| onRollOut | up | when mouse leaves entity, after || | | onRollOver |+------------------+---------------+-------------------------------------+| onPress | up -> down | sent to topmost entity when mouse || | | button goes down. onRollOver || | | always precedes onPress. Initiates || | | mouse capture. |+------------------+---------------+-------------------------------------+| onRelease | down -> up | sent to active entity if mouse goes || | | up while over the element |+------------------+---------------+-------------------------------------+| onDragOut | down | sent to active entity if mouse || | | is no longer over the entity |+------------------+---------------+-------------------------------------+| onReleaseOutside | down -> up | sent to active entity if mouse goes || | | up while not over the entity. || | | onDragOut always precedes || | | onReleaseOutside |+------------------+---------------+-------------------------------------+| onDragOver | down | sent to active entity if mouse is || | | dragged back over it after || | | onDragOut |+------------------+---------------+-------------------------------------+There is always one active entity at any given time (considering NULL tobe an active entity, representing the background, and other objects thatdon't receive mouse events).When the mouse button is up, the active entity is the topmost elementdirectly under the mouse pointer.When the mouse button is down, the active entity remains whatever itwas when the button last went down.The active entity is the only object that receives mouse events.!!! The "trackAsMenu" property alters this behavior! If trackAsMenuis set on the active entity, then onReleaseOutside is filtered out,and onDragOver from another entity is allowed (from the background, oranother trackAsMenu entity). !!!Pseudocode:active_entity = NULLmouse_button_state = UPmouse_inside_entity_state = falseframe loop: if mouse_button_state == DOWN // Handle trackAsMenu if (active_entity->trackAsMenu) possible_entity = topmost entity below mouse if (possible_entity != active_entity && possible_entity->trackAsMenu) // Transfer to possible entity active_entity = possible_entity active_entity->onDragOver() mouse_inside_entity_state = true; // Handle onDragOut, onDragOver if (mouse_inside_entity_state == false) if (mouse is actually inside the active_entity) // onDragOver active_entity->onDragOver() mouse_inside_entity_state = true; else // mouse_inside_entity_state == true if (mouse is actually outside the active_entity) // onDragOut active_entity->onDragOut() mouse_inside_entity_state = false; // Handle onRelease, onReleaseOutside if (mouse button is up) if (mouse_inside_entity_state) // onRelease active_entity->onRelease() else // onReleaseOutside if (active_entity->trackAsMenu == false) active_entity->onReleaseOutside() mouse_button_state = UP if mouse_button_state == UP new_active_entity = topmost entity below the mouse if (new_active_entity != active_entity) // onRollOut, onRollOver active_entity->onRollOut() active_entity = new_active_entity active_entity->onRollOver() // Handle press if (mouse button is down) // onPress active_entity->onPress() mouse_inside_entity_state = true mouse_button_state = DOWN*/namespace gameswf{ void generate_mouse_button_events(mouse_button_state* ms) { smart_ptr<movie> active_entity = ms->m_active_entity; smart_ptr<movie> topmost_entity = ms->m_topmost_entity; if (ms->m_mouse_button_state_last == 1) { // Mouse button was down. // Handle trackAsMenu dragOver if (active_entity == NULL || active_entity->get_track_as_menu()) { if (topmost_entity != NULL && topmost_entity != active_entity && topmost_entity->get_track_as_menu() == true) { // Transfer to topmost entity, dragOver active_entity = topmost_entity; active_entity->on_button_event(event_id::DRAG_OVER); ms->m_mouse_inside_entity_last = true; } } // Handle onDragOut, onDragOver if (ms->m_mouse_inside_entity_last == false) { if (topmost_entity == active_entity) { // onDragOver if (active_entity != NULL) { active_entity->on_button_event(event_id::DRAG_OVER); } ms->m_mouse_inside_entity_last = true; } } else { // mouse_inside_entity_last == true if (topmost_entity != active_entity) { // onDragOut if (active_entity != NULL) { active_entity->on_button_event(event_id::DRAG_OUT); } ms->m_mouse_inside_entity_last = false; } } // Handle onRelease, onReleaseOutside if (ms->m_mouse_button_state_current == 0) { // Mouse button just went up. ms->m_mouse_button_state_last = 0; if (active_entity != NULL) { if (ms->m_mouse_inside_entity_last) { // onRelease active_entity->on_button_event(event_id::RELEASE); } else { // onReleaseOutside if (active_entity->get_track_as_menu() == false) { active_entity->on_button_event(event_id::RELEASE_OUTSIDE); } } } } } if (ms->m_mouse_button_state_last == 0) { // Mouse button was up. // New active entity is whatever is below the mouse right now. if (topmost_entity != active_entity) { // onRollOut if (active_entity != NULL) { active_entity->on_button_event(event_id::ROLL_OUT); } active_entity = topmost_entity; // onRollOver if (active_entity != NULL) { active_entity->on_button_event(event_id::ROLL_OVER); } ms->m_mouse_inside_entity_last = true; } // mouse button press if (ms->m_mouse_button_state_current == 1) { // onPress if (active_entity != NULL) { active_entity->on_button_event(event_id::PRESS); } ms->m_mouse_inside_entity_last = true; ms->m_mouse_button_state_last = 1; } } // Write the (possibly modified) smart_ptr copies back // into the state struct. ms->m_active_entity = active_entity; ms->m_topmost_entity = topmost_entity; } struct button_character_instance : public character { button_character_definition* m_def; array< smart_ptr<character> > m_record_character; enum mouse_flags { IDLE = 0, FLAG_OVER = 1, FLAG_DOWN = 2, OVER_DOWN = FLAG_OVER|FLAG_DOWN, // aliases OVER_UP = FLAG_OVER, OUT_DOWN = FLAG_DOWN }; int m_last_mouse_flags, m_mouse_flags; enum e_mouse_state { UP = 0, DOWN, OVER }; e_mouse_state m_mouse_state; button_character_instance(button_character_definition* def, movie* parent, int id) : character(parent, id), m_def(def), m_last_mouse_flags(IDLE), m_mouse_flags(IDLE), m_mouse_state(UP) { assert(m_def); int r, r_num = m_def->m_button_records.size(); m_record_character.resize(r_num); movie_definition_sub* movie_def = static_cast<movie_definition_sub*>( parent->get_root_movie()->get_movie_definition()); for (r = 0; r < r_num; r++) { button_record* bdef = &m_def->m_button_records[r]; if (bdef->m_character_def == NULL) { // Resolve the character id. bdef->m_character_def = movie_def->get_character_def(bdef->m_character_id); } assert(bdef->m_character_def != NULL); const matrix& mat = m_def->m_button_records[r].m_button_matrix; const cxform& cx = m_def->m_button_records[r].m_button_cxform; smart_ptr<character> ch = bdef->m_character_def->create_character_instance(this, id); m_record_character[r] = ch; ch->set_matrix(mat); ch->set_cxform(cx); ch->restart(); } } ~button_character_instance() { } movie_root* get_root() { return get_parent()->get_root(); } movie* get_root_movie() { return get_parent()->get_root_movie(); } void restart() { m_last_mouse_flags = IDLE; m_mouse_flags = IDLE; m_mouse_state = UP; int r, r_num = m_record_character.size(); for (r = 0; r < r_num; r++) { m_record_character[r]->restart(); } } virtual void advance(float delta_time) { // Implement mouse-drag. character::do_mouse_drag(); matrix mat = get_world_matrix(); // Advance our relevant characters. {for (int i = 0; i < m_def->m_button_records.size(); i++) { button_record& rec = m_def->m_button_records[i]; if (m_record_character[i] == NULL) { continue; } // Matrix matrix sub_matrix = mat; sub_matrix.concatenate(rec.m_button_matrix); // Advance characters that are activated by the new mouse state if (((m_mouse_state == UP) && (rec.m_up)) || ((m_mouse_state == DOWN) && (rec.m_down)) || ((m_mouse_state == OVER) && (rec.m_over))) { m_record_character[i]->advance(delta_time); } }} } void display() { for (int i = 0; i < m_def->m_button_records.size(); i++) { button_record& rec = m_def->m_button_records[i]; if (m_record_character[i] == NULL) { continue; } if ((m_mouse_state == UP && rec.m_up) || (m_mouse_state == DOWN && rec.m_down) || (m_mouse_state == OVER && rec.m_over)) { m_record_character[i]->display(); } } do_display_callback(); } inline int transition(int a, int b) const // Combine the flags to avoid a conditional. It would be faster with a macro. { return (a << 2) | b; } virtual movie* get_topmost_mouse_entity(float x, float y) // Return the topmost entity that the given point covers. NULL if none. // I.e. check against ourself. { if (get_visible() == false) { return false; } matrix m = get_matrix(); point p; m.transform_by_inverse(&p, point(x, y)); {for (int i = 0; i < m_def->m_button_records.size(); i++) { button_record& rec = m_def->m_button_records[i]; if (rec.m_character_id < 0 || rec.m_hit_test == false) { continue; } // Find the mouse position in button-record space. point sub_p; rec.m_button_matrix.transform_by_inverse(&sub_p, p); if (rec.m_character_def->point_test_local(sub_p.m_x, sub_p.m_y)) { // The mouse is inside the shape. return this; // @@ Are there any circumstances where this is correct: //return m_record_character[i].get_ptr(); } }} return NULL; } virtual void on_button_event(event_id event) { // Set our mouse state (so we know how to render). switch (event.m_id) { case event_id::ROLL_OUT: case event_id::RELEASE_OUTSIDE: m_mouse_state = UP; break; case event_id::RELEASE: case event_id::ROLL_OVER: case event_id::DRAG_OUT: m_mouse_state = OVER; break; case event_id::PRESS: case event_id::DRAG_OVER: m_mouse_state = DOWN; break; default: assert(0); // missed a case? break; }; // Button transition sounds. if (m_def->m_sound != NULL) { int bi; // button sound array index [0..3] sound_handler* s = get_sound_handler(); // Check if there is a sound handler if (s != NULL) { switch (event.m_id) { case event_id::ROLL_OUT: bi = 0; break; case event_id::ROLL_OVER: bi = 1; break; case event_id::PRESS: bi = 2; break; case event_id::RELEASE: bi = 3; break; default: bi = -1; break; } if (bi >= 0) { button_character_definition::button_sound_info& bs = m_def->m_sound->m_button_sounds[bi]; // character zero is considered as null character if (bs.m_sound_id > 0) { assert(m_def->m_sound->m_button_sounds[bi].m_sam != NULL); if (bs.m_sound_style.m_stop_playback)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -