/* * This source file is part of libRocket, the HTML/CSS Interface Middleware * * For the latest information, see http://www.librocket.com * * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #include "precompiled.h" #include "../../Include/Rocket/Core.h" #include "EventDispatcher.h" #include "EventIterators.h" #include "PluginRegistry.h" #include "StreamFile.h" #include "../../Include/Rocket/Core/StreamMemory.h" #include #include namespace Rocket { namespace Core { const float DOUBLE_CLICK_TIME = 0.5f; Context::Context(const String& name) : name(name), dimensions(0, 0), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1), density_independent_pixel_ratio(1.0f), view_state() { instancer = NULL; // Initialise this to NULL; this will be set in Rocket::Core::CreateContext(). render_interface = NULL; root = Factory::InstanceElement(NULL, "*", "#root", XMLAttributes()); root->SetId(name); root->SetOffset(Vector2f(0, 0), NULL); root->SetProperty(Z_INDEX, "0"); Element* element = Factory::InstanceElement(NULL, "body", "body", XMLAttributes()); cursor_proxy = dynamic_cast< ElementDocument* >(element); if (cursor_proxy == NULL) { if (element != NULL) element->RemoveReference(); } else cursor_proxy->context = this; document_focus_history.push_back(root); focus = root; show_cursor = true; drag_started = false; drag_verbose = false; drag_clone = NULL; last_click_element = NULL; last_click_time = 0; } Context::~Context() { PluginRegistry::NotifyContextDestroy(this); UnloadAllDocuments(); UnloadAllMouseCursors(); ReleaseUnloadedDocuments(); if (cursor_proxy != NULL) cursor_proxy->RemoveReference(); if (root != NULL) root->RemoveReference(); if (instancer) instancer->RemoveReference(); if (render_interface) render_interface->RemoveReference(); } // Returns the name of the context. const String& Context::GetName() const { return name; } // Changes the dimensions of the screen. void Context::SetDimensions(const Vector2i& _dimensions) { if (dimensions != _dimensions) { dimensions = _dimensions; root->SetBox(Box(Vector2f((float) dimensions.x, (float) dimensions.y))); root->DirtyLayout(); for (int i = 0; i < root->GetNumChildren(); ++i) { ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); if (document != NULL) { document->DirtyLayout(); document->UpdatePosition(); } // We need to rebuild our projection matrices when the // dimensions change. root->GetChild(i)->DirtyTransformState(true, false, false); } clip_dimensions = dimensions; // TODO: Ensure the user calls ProcessProjectionChange() before // the next rendering phase. } } // Returns the dimensions of the screen. const Vector2i& Context::GetDimensions() const { return dimensions; } // Returns the current state of the view. const ViewState& Context::GetViewState() const noexcept { return view_state; } void Context::SetDensityIndependentPixelRatio(float _density_independent_pixel_ratio) { if (density_independent_pixel_ratio != _density_independent_pixel_ratio) { density_independent_pixel_ratio = _density_independent_pixel_ratio; for (int i = 0; i < root->GetNumChildren(); ++i) { ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); if (document != NULL) { document->DirtyDpProperties(); } } } } float Context::GetDensityIndependentPixelRatio() const { return density_independent_pixel_ratio; } // Updates all elements in the element tree. bool Context::Update() { #ifdef _DEBUG // Reset all document layout locks (work-around due to leak, possibly in select element?) for (int i = 0; i < root->GetNumChildren(); ++i) { ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); if (document != NULL && document->lock_layout != 0) { Log::Message(Log::LT_WARNING, "Layout lock leak. Document '%s' had lock layout value: %d", document->title.CString(), document->lock_layout); document->lock_layout = 0; } } #endif root->Update(); // Release any documents that were unloaded during the update. ReleaseUnloadedDocuments(); return true; } // Renders all visible elements in the element tree. bool Context::Render() { RenderInterface* render_interface = GetRenderInterface(); if (render_interface == NULL) return false; // Update the layout for all documents in the root. This is done now as events during the // update may have caused elements to require an update. for (int i = 0; i < root->GetNumChildren(); ++i) if (auto doc = root->GetChild(i)->GetOwnerDocument()) doc->UpdateLayout(true); render_interface->context = this; ElementUtilities::ApplyActiveClipRegion(this, render_interface); root->Render(); ElementUtilities::SetClippingRegion(NULL, this); // Render the cursor proxy so any elements attached the cursor will be rendered below the cursor. if (cursor_proxy != NULL) { cursor_proxy->Update(); cursor_proxy->SetOffset(Vector2f((float)Math::Clamp(mouse_position.x, 0, dimensions.x), (float)Math::Clamp(mouse_position.y, 0, dimensions.y)), NULL); cursor_proxy->Render(); } // Render the cursor document if we have one and we're showing the cursor. if (active_cursor && show_cursor) { active_cursor->Update(); active_cursor->SetOffset(Vector2f((float)Math::Clamp(mouse_position.x, 0, dimensions.x), (float)Math::Clamp(mouse_position.y, 0, dimensions.y)), NULL); active_cursor->Render(); } render_interface->context = NULL; return true; } // Creates a new, empty document and places it into this context. ElementDocument* Context::CreateDocument(const String& tag) { Element* element = Factory::InstanceElement(NULL, tag, "body", XMLAttributes()); if (element == NULL) { Log::Message(Log::LT_ERROR, "Failed to instance document on tag '%s', instancer returned NULL.", tag.CString()); return NULL; } ElementDocument* document = dynamic_cast< ElementDocument* >(element); if (document == NULL) { Log::Message(Log::LT_ERROR, "Failed to instance document on tag '%s', Found type '%s', was expecting derivative of ElementDocument.", tag.CString(), typeid(element).name()); element->RemoveReference(); return NULL; } document->context = this; root->AppendChild(document); PluginRegistry::NotifyDocumentLoad(document); return document; } // Load a document into the context. ElementDocument* Context::LoadDocument(const String& document_path) { // Open the stream based on the file path StreamFile* stream = new StreamFile(); if (!stream->Open(document_path)) { stream->RemoveReference(); return NULL; } // Load the document from the stream ElementDocument* document = LoadDocument(stream); stream->RemoveReference(); return document; } // Load a document into the context. ElementDocument* Context::LoadDocument(Stream* stream) { PluginRegistry::NotifyDocumentOpen(this, stream->GetSourceURL().GetURL()); // Load the document from the stream. ElementDocument* document = Factory::InstanceDocumentStream(this, stream); if (!document) return NULL; root->AppendChild(document); // Bind the events, run the layout and fire the 'onload' event. ElementUtilities::BindEventAttributes(document); document->UpdateLayout(); // Dispatch the load notifications. PluginRegistry::NotifyDocumentLoad(document); document->DispatchEvent(LOAD, Dictionary(), false); return document; } // Load a document into the context. ElementDocument* Context::LoadDocumentFromMemory(const String& string) { // Open the stream based on the string contents. StreamMemory* stream = new StreamMemory((byte*)string.CString(), string.Length()); stream->SetSourceURL("[document from memory]"); // Load the document from the stream. ElementDocument* document = LoadDocument(stream); stream->RemoveReference(); return document; } // Unload the given document void Context::UnloadDocument(ElementDocument* _document) { // Has this document already been unloaded? for (size_t i = 0; i < unloaded_documents.size(); ++i) { if (unloaded_documents[i] == _document) return; } // Add a reference, to ensure the document isn't released // while we're closing it. unloaded_documents.push_back(_document); ElementDocument* document = _document; if (document->GetParentNode() == root) { // Dispatch the unload notifications. document->DispatchEvent(UNLOAD, Dictionary(), false); PluginRegistry::NotifyDocumentUnload(document); // Remove the document from the context. root->RemoveChild(document); } // Remove the item from the focus history. ElementList::iterator itr = std::find(document_focus_history.begin(), document_focus_history.end(), document); if (itr != document_focus_history.end()) document_focus_history.erase(itr); // Focus to the previous document if the old document is the current focus. if (focus && focus->GetOwnerDocument() == document) { focus = NULL; document_focus_history.back()->GetFocusLeafNode()->Focus(); } // Clear the active element if the old document is the active element. if (active && active->GetOwnerDocument() == document) { active = NULL; } // Rebuild the hover state. UpdateHoverChain(Dictionary(), Dictionary(), mouse_position); } // Unload all the currently loaded documents void Context::UnloadAllDocuments() { // Unload all children. while (root->GetNumChildren(true) > 0) UnloadDocument(root->GetChild(0)->GetOwnerDocument()); // Force cleanup of child elements now, reference counts must hit zero so that python (if it's in use) cleans up // before we exit this method. root->active_children.clear(); root->ReleaseElements(root->deleted_children); } // Adds a previously-loaded cursor document as a mouse cursor within this context. void Context::AddMouseCursor(ElementDocument* cursor_document) { cursor_document->AddReference(); CursorMap::iterator i = cursors.find(cursor_document->GetTitle()); if (i != cursors.end()) { if (active_cursor == (*i).second) active_cursor = cursor_document; if (default_cursor == (*i).second) default_cursor = cursor_document; (*i).second->RemoveReference(); } cursors[cursor_document->GetTitle()] = cursor_document; if (!default_cursor) { default_cursor = cursor_document; active_cursor = cursor_document; } } // Loads a document as a mouse cursor. ElementDocument* Context::LoadMouseCursor(const String& document_path) { StreamFile* stream = new StreamFile(); if (!stream->Open(document_path)) return NULL; // Load the document from the stream. ElementDocument* document = Factory::InstanceDocumentStream(this, stream); if (document == NULL) { stream->RemoveReference(); return NULL; } AddMouseCursor(document); // Bind the events, run the layout and fire the 'onload' event. ElementUtilities::BindEventAttributes(document); document->UpdateLayout(); document->DispatchEvent(LOAD, Dictionary(), false); stream->RemoveReference(); return document; } // Unload the given cursor. void Context::UnloadMouseCursor(const String& cursor_name) { CursorMap::iterator i = cursors.find(cursor_name); if (i != cursors.end()) { if (default_cursor == (*i).second) default_cursor = NULL; if (active_cursor == (*i).second) active_cursor = default_cursor; (*i).second->RemoveReference(); cursors.erase(i); } } // Unloads all currently loaded cursors. void Context::UnloadAllMouseCursors() { while (!cursors.empty()) UnloadMouseCursor((*cursors.begin()).first.CString()); } // Sets a cursor as the active cursor. bool Context::SetMouseCursor(const String& cursor_name) { CursorMap::iterator i = cursors.find(cursor_name); if (i == cursors.end()) { active_cursor = default_cursor; Log::Message(Log::LT_WARNING, "Failed to find cursor '%s' in context '%s', reverting to default cursor.", cursor_name.CString(), name.CString()); return false; } active_cursor = (*i).second; return true; } // Shows or hides the cursor. void Context::ShowMouseCursor(bool show) { show_cursor = show; } // Returns the first document found in the root with the given id. ElementDocument* Context::GetDocument(const String& id) { for (int i = 0; i < root->GetNumChildren(); i++) { ElementDocument* document = root->GetChild(i)->GetOwnerDocument(); if (document == NULL) continue; if (document->GetId() == id) return document; } return NULL; } // Returns a document in the context by index. ElementDocument* Context::GetDocument(int index) { Element* element = root->GetChild(index); if (element == NULL) return NULL; return element->GetOwnerDocument(); } // Returns the number of documents in the context. int Context::GetNumDocuments() const { return root->GetNumChildren(); } // Returns the hover element. Element* Context::GetHoverElement() { return *hover; } // Returns the focus element. Element* Context::GetFocusElement() { return *focus; } // Returns the root element. Element* Context::GetRootElement() { return root; } // Brings the document to the front of the document stack. void Context::PullDocumentToFront(ElementDocument* document) { if (document != root->GetLastChild()) { // Calling RemoveChild() / AppendChild() would be cleaner, but that dirties the document's layout // unnecessarily, so we'll go under the hood here. for (int i = 0; i < root->GetNumChildren(); ++i) { if (root->GetChild(i) == document) { root->children.erase(root->children.begin() + i); root->children.insert(root->children.begin() + root->GetNumChildren(), document); root->DirtyStackingContext(); } } } } // Sends the document to the back of the document stack. void Context::PushDocumentToBack(ElementDocument* document) { if (document != root->GetFirstChild()) { // See PullDocumentToFront(). for (int i = 0; i < root->GetNumChildren(); ++i) { if (root->GetChild(i) == document) { root->children.erase(root->children.begin() + i); root->children.insert(root->children.begin(), document); root->DirtyStackingContext(); } } } } // Adds an event listener to the root element. void Context::AddEventListener(const String& event, EventListener* listener, bool in_capture_phase) { root->AddEventListener(event, listener, in_capture_phase); } // Removes an event listener from the root element. void Context::RemoveEventListener(const String& event, EventListener* listener, bool in_capture_phase) { root->RemoveEventListener(event, listener, in_capture_phase); } // Sends a key down event into Rocket. bool Context::ProcessKeyDown(Input::KeyIdentifier key_identifier, int key_modifier_state) { // Generate the parameters for the key event. Dictionary parameters; GenerateKeyEventParameters(parameters, key_identifier); GenerateKeyModifierEventParameters(parameters, key_modifier_state); if (focus) return focus->DispatchEvent(KEYDOWN, parameters, true); else return root->DispatchEvent(KEYDOWN, parameters, true); } // Sends a key up event into Rocket. bool Context::ProcessKeyUp(Input::KeyIdentifier key_identifier, int key_modifier_state) { // Generate the parameters for the key event. Dictionary parameters; GenerateKeyEventParameters(parameters, key_identifier); GenerateKeyModifierEventParameters(parameters, key_modifier_state); if (focus) return focus->DispatchEvent(KEYUP, parameters, true); else return root->DispatchEvent(KEYUP, parameters, true); } // Sends a single character of text as text input into Rocket. bool Context::ProcessTextInput(word character) { // Generate the parameters for the key event. Dictionary parameters; parameters.Set("data", character); if (focus) return focus->DispatchEvent(TEXTINPUT, parameters, true); else return root->DispatchEvent(TEXTINPUT, parameters, true); } // Sends a string of text as text input into Rocket. bool Context::ProcessTextInput(const String& string) { bool consumed = true; for (size_t i = 0; i < string.Length(); ++i) { // Generate the parameters for the key event. Dictionary parameters; parameters.Set("data", string[i]); if (focus) consumed = focus->DispatchEvent(TEXTINPUT, parameters, true) && consumed; else consumed = root->DispatchEvent(TEXTINPUT, parameters, true) && consumed; } return consumed; } // Sends a mouse movement event into Rocket. void Context::ProcessMouseMove(int x, int y, int key_modifier_state) { // Check whether the mouse moved since the last event came through. Vector2i old_mouse_position = mouse_position; bool mouse_moved = (x != mouse_position.x) || (y != mouse_position.y); if (mouse_moved) { mouse_position.x = x; mouse_position.y = y; } // Generate the parameters for the mouse events (there could be a few!). Dictionary parameters; GenerateMouseEventParameters(parameters, -1); GenerateKeyModifierEventParameters(parameters, key_modifier_state); Dictionary drag_parameters; GenerateMouseEventParameters(drag_parameters); GenerateDragEventParameters(drag_parameters); GenerateKeyModifierEventParameters(drag_parameters, key_modifier_state); // Update the current hover chain. This will send all necessary 'onmouseout', 'onmouseover', 'ondragout' and // 'ondragover' messages. UpdateHoverChain(parameters, drag_parameters, old_mouse_position); // Dispatch any 'onmousemove' events. if (mouse_moved) { if (hover) { hover->DispatchEvent(MOUSEMOVE, parameters, true); if (drag_hover && drag_verbose) drag_hover->DispatchEvent(DRAGMOVE, drag_parameters, true); } } } static Element* FindFocusElement(Element* element) { ElementDocument* owner_document = element->GetOwnerDocument(); if (!owner_document || owner_document->GetProperty< int >(FOCUS) == FOCUS_NONE) return NULL; while (element && element->GetProperty< int >(FOCUS) == FOCUS_NONE) { element = element->GetParentNode(); } return element; } // Sends a mouse-button down event into Rocket. void Context::ProcessMouseButtonDown(int button_index, int key_modifier_state) { Dictionary parameters; GenerateMouseEventParameters(parameters, button_index); GenerateKeyModifierEventParameters(parameters, key_modifier_state); if (button_index == 0) { Element* new_focus = *hover; // Set the currently hovered element to focus if it isn't already the focus. if (hover) { new_focus = FindFocusElement(*hover); if (new_focus && new_focus != *focus) { if (!new_focus->Focus()) return; } } // Save the just-pressed-on element as the pressed element. active = new_focus; bool propogate = true; // Call 'onmousedown' on every item in the hover chain, and copy the hover chain to the active chain. if (hover) propogate = hover->DispatchEvent(MOUSEDOWN, parameters, true); if (propogate) { // Check for a double-click on an element; if one has occured, we send the 'dblclick' event to the hover // element. If not, we'll start a timer to catch the next one. float click_time = GetSystemInterface()->GetElapsedTime(); if (active == last_click_element && click_time - last_click_time < DOUBLE_CLICK_TIME) { if (hover) propogate = hover->DispatchEvent(DBLCLICK, parameters, true); last_click_element = NULL; last_click_time = 0; } else { last_click_element = *active; last_click_time = click_time; } } for (ElementSet::iterator itr = hover_chain.begin(); itr != hover_chain.end(); ++itr) active_chain.push_back((*itr)); if (propogate) { // Traverse down the hierarchy of the newly focussed element (if any), and see if we can begin dragging it. drag_started = false; drag = hover; while (drag) { int drag_style = drag->GetProperty(DRAG)->value.Get< int >(); switch (drag_style) { case DRAG_NONE: drag = drag->GetParentNode(); continue; case DRAG_BLOCK: drag = NULL; continue; default: drag_verbose = (drag_style == DRAG_DRAG_DROP || drag_style == DRAG_CLONE); } break; } } } else { // Not the primary mouse button, so we're not doing any special processing. if (hover) hover->DispatchEvent(MOUSEDOWN, parameters, true); } } // Sends a mouse-button up event into Rocket. void Context::ProcessMouseButtonUp(int button_index, int key_modifier_state) { Dictionary parameters; GenerateMouseEventParameters(parameters, button_index); GenerateKeyModifierEventParameters(parameters, key_modifier_state); // Process primary click. if (button_index == 0) { // The elements in the new hover chain have the 'onmouseup' event called on them. if (hover) hover->DispatchEvent(MOUSEUP, parameters, true); // If the active element (the one that was being hovered over when the mouse button was pressed) is still being // hovered over, we click it. if (hover && active && active == FindFocusElement(*hover)) { active->DispatchEvent(CLICK, parameters, true); } // Unset the 'active' pseudo-class on all the elements in the active chain; because they may not necessarily // have had 'onmouseup' called on them, we can't guarantee this has happened already. std::for_each(active_chain.begin(), active_chain.end(), PseudoClassFunctor("active", false)); active_chain.clear(); if (drag) { if (drag_started) { Dictionary drag_parameters; GenerateMouseEventParameters(drag_parameters); GenerateDragEventParameters(drag_parameters); GenerateKeyModifierEventParameters(drag_parameters, key_modifier_state); if (drag_hover) { if (drag_verbose) { drag_hover->DispatchEvent(DRAGDROP, drag_parameters, true); drag_hover->DispatchEvent(DRAGOUT, drag_parameters, true); } } drag->DispatchEvent(DRAGEND, drag_parameters, true); ReleaseDragClone(); } drag = NULL; drag_hover = NULL; drag_hover_chain.clear(); } } else { // Not the left mouse button, so we're not doing any special processing. if (hover) hover->DispatchEvent(MOUSEUP, parameters, true); } } // Sends a mouse-wheel movement event into Rocket. bool Context::ProcessMouseWheel(int wheel_delta, int key_modifier_state) { if (hover) { Dictionary scroll_parameters; GenerateKeyModifierEventParameters(scroll_parameters, key_modifier_state); scroll_parameters.Set("wheel_delta", wheel_delta); return hover->DispatchEvent(MOUSESCROLL, scroll_parameters, true); } return true; } // Notifies Rocket of a change in the projection matrix. void Context::ProcessProjectionChange(const Matrix4f &projection) { view_state.SetProjection(&projection); } // Notifies Rocket of a change in the view matrix. void Context::ProcessViewChange(const Matrix4f &view) { view_state.SetView(&view); } // Gets the context's render interface. RenderInterface* Context::GetRenderInterface() const { return render_interface; } // Gets the current clipping region for the render traversal bool Context::GetActiveClipRegion(Vector2i& origin, Vector2i& dimensions) const { if (clip_dimensions.x < 0 || clip_dimensions.y < 0) return false; origin = clip_origin; dimensions = clip_dimensions; return true; } // Sets the current clipping region for the render traversal void Context::SetActiveClipRegion(const Vector2i& origin, const Vector2i& dimensions) { clip_origin = origin; clip_dimensions = dimensions; } // Sets the instancer to use for releasing this object. void Context::SetInstancer(ContextInstancer* _instancer) { ROCKET_ASSERT(instancer == NULL); instancer = _instancer; instancer->AddReference(); } // Internal callback for when an element is removed from the hierarchy. void Context::OnElementRemove(Element* element) { ElementSet::iterator i = hover_chain.find(element); if (i == hover_chain.end()) return; ElementSet old_hover_chain = hover_chain; hover_chain.erase(i); Element* hover_element = element; while (hover_element != NULL) { Element* next_hover_element = NULL; // Look for a child on this element's children that is also hovered. for (int j = 0; j < hover_element->GetNumChildren(true); ++j) { // Is this child also in the hover chain? Element* hover_child_element = hover_element->GetChild(j); ElementSet::iterator k = hover_chain.find(hover_child_element); if (k != hover_chain.end()) { next_hover_element = hover_child_element; hover_chain.erase(k); break; } } hover_element = next_hover_element; } Dictionary parameters; GenerateMouseEventParameters(parameters, -1); SendEvents(old_hover_chain, hover_chain, MOUSEOUT, parameters, true); } // Internal callback for when a new element gains focus bool Context::OnFocusChange(Element* new_focus) { ElementSet old_chain; ElementSet new_chain; Element* old_focus = *(focus); ElementDocument* old_document = old_focus ? old_focus->GetOwnerDocument() : NULL; ElementDocument* new_document = new_focus->GetOwnerDocument(); // If the current focus is modal and the new focus is not modal, deny the request if (old_document && old_document->IsModal() && (!new_document || !new_document->GetOwnerDocument()->IsModal())) return false; // Build the old chains Element* element = old_focus; while (element) { old_chain.insert(element); element = element->GetParentNode(); } // Build the new chain element = new_focus; while (element) { new_chain.insert(element); element = element->GetParentNode(); } Dictionary parameters; // Send out blur/focus events. SendEvents(old_chain, new_chain, BLUR, parameters, false); SendEvents(new_chain, old_chain, FOCUS, parameters, false); focus = new_focus; // Raise the element's document to the front, if desired. ElementDocument* document = focus->GetOwnerDocument(); if (document != NULL) { const Property* z_index_property = document->GetProperty(Z_INDEX); if (z_index_property->unit == Property::KEYWORD && z_index_property->value.Get< int >() == Z_INDEX_AUTO) document->PullToFront(); } // Update the focus history if (old_document != new_document) { // If documents have changed, add the new document to the end of the history ElementList::iterator itr = std::find(document_focus_history.begin(), document_focus_history.end(), new_document); if (itr != document_focus_history.end()) document_focus_history.erase(itr); if (new_document != NULL) document_focus_history.push_back(new_document); } return true; } // Generates an event for faking clicks on an element. void Context::GenerateClickEvent(Element* element) { Dictionary parameters; GenerateMouseEventParameters(parameters, 0); element->DispatchEvent(CLICK, parameters, true); } // Updates the current hover elements, sending required events. void Context::UpdateHoverChain(const Dictionary& parameters, const Dictionary& drag_parameters, const Vector2i& old_mouse_position) { Vector2f position((float) mouse_position.x, (float) mouse_position.y); // Send out drag events. if (drag) { if (mouse_position != old_mouse_position) { if (!drag_started) { Dictionary drag_start_parameters = drag_parameters; drag_start_parameters.Set("mouse_x", old_mouse_position.x); drag_start_parameters.Set("mouse_y", old_mouse_position.y); drag->DispatchEvent(DRAGSTART, drag_start_parameters); drag_started = true; if (drag->GetProperty< int >(DRAG) == DRAG_CLONE) { // Clone the element and attach it to the mouse cursor. CreateDragClone(*drag); } } drag->DispatchEvent(DRAG, drag_parameters); } } hover = GetElementAtPoint(position); if (!hover || hover->GetProperty(CURSOR)->unit == Property::KEYWORD) active_cursor = default_cursor; else SetMouseCursor(hover->GetProperty< String >(CURSOR)); // Build the new hover chain. ElementSet new_hover_chain; Element* element = *hover; while (element != NULL) { new_hover_chain.insert(element); element = element->GetParentNode(); } // Send mouseout / mouseover events. SendEvents(hover_chain, new_hover_chain, MOUSEOUT, parameters, true); SendEvents(new_hover_chain, hover_chain, MOUSEOVER, parameters, true); // Send out drag events. if (drag) { drag_hover = GetElementAtPoint(position, *drag); ElementSet new_drag_hover_chain; element = *drag_hover; while (element != NULL) { new_drag_hover_chain.insert(element); element = element->GetParentNode(); } /* if (mouse_moved && !drag_started) { drag->DispatchEvent(DRAGSTART, drag_parameters); drag_started = true; if (drag->GetProperty< int >(DRAG) == DRAG_CLONE) { // Clone the element and attach it to the mouse cursor. CreateDragClone(*drag); } }*/ if (drag_started && drag_verbose) { // Send out ondragover and ondragout events as appropriate. SendEvents(drag_hover_chain, new_drag_hover_chain, DRAGOUT, drag_parameters, true); SendEvents(new_drag_hover_chain, drag_hover_chain, DRAGOVER, drag_parameters, true); } drag_hover_chain.swap(new_drag_hover_chain); } // Swap the new chain in. hover_chain.swap(new_hover_chain); } // Returns the youngest descendent of the given element which is under the given point in screen coodinates. Element* Context::GetElementAtPoint(const Vector2f& point, const Element* ignore_element, Element* element) { // Update the layout on all documents prior to this call. for (int i = 0; i < GetNumDocuments(); ++i) GetDocument(i)->UpdateLayout(); if (element == NULL) { if (ignore_element == root) return NULL; element = root; } // Check if any documents have modal focus; if so, only check down than document. if (element == root) { if (focus) { ElementDocument* focus_document = focus->GetOwnerDocument(); if (focus_document != NULL && focus_document->IsModal()) { element = focus_document; } } } // Check any elements within our stacking context. We want to return the lowest-down element // that is under the cursor. if (element->local_stacking_context) { if (element->stacking_context_dirty) element->BuildLocalStackingContext(); for (int i = (int) element->stacking_context.size() - 1; i >= 0; --i) { if (ignore_element != NULL) { Element* element_hierarchy = element->stacking_context[i]; while (element_hierarchy != NULL) { if (element_hierarchy == ignore_element) break; element_hierarchy = element_hierarchy->GetParentNode(); } if (element_hierarchy != NULL) continue; } Element* child_element = GetElementAtPoint(point, ignore_element, element->stacking_context[i]); if (child_element != NULL) return child_element; } } // Ignore elements whose pointer events are disabled if (element->GetPointerEvents() == POINTER_EVENTS_NONE) return NULL; Vector2f projected_point = element->Project(point); // Check if the point is actually within this element. bool within_element = element->IsPointWithinElement(projected_point); if (within_element) { Vector2i clip_origin, clip_dimensions; if (ElementUtilities::GetClippingRegion(clip_origin, clip_dimensions, element)) { within_element = projected_point.x >= clip_origin.x && projected_point.y >= clip_origin.y && projected_point.x <= (clip_origin.x + clip_dimensions.x) && projected_point.y <= (clip_origin.y + clip_dimensions.y); } } if (within_element) return element; return NULL; } // Creates the drag clone from the given element. void Context::CreateDragClone(Element* element) { if (cursor_proxy == NULL) { Log::Message(Log::LT_ERROR, "Unable to create drag clone, no cursor proxy document."); return; } ReleaseDragClone(); // Instance the drag clone. drag_clone = element->Clone(); if (drag_clone == NULL) { Log::Message(Log::LT_ERROR, "Unable to duplicate drag clone."); return; } // Append the clone to the cursor proxy element. cursor_proxy->AppendChild(drag_clone); drag_clone->RemoveReference(); // Set the style sheet on the cursor proxy. cursor_proxy->SetStyleSheet(element->GetStyleSheet()); // Set all the required properties and pseudo-classes on the clone. drag_clone->SetPseudoClass("drag", true); drag_clone->SetProperty("position", "absolute"); drag_clone->SetProperty("left", Property(element->GetAbsoluteLeft() - element->GetBox().GetEdge(Box::MARGIN, Box::LEFT) - mouse_position.x, Property::PX)); drag_clone->SetProperty("top", Property(element->GetAbsoluteTop() - element->GetBox().GetEdge(Box::MARGIN, Box::TOP) - mouse_position.y, Property::PX)); } // Releases the drag clone, if one exists. void Context::ReleaseDragClone() { if (drag_clone != NULL) { cursor_proxy->RemoveChild(drag_clone); drag_clone = NULL; } } // Builds the parameters for a generic key event. void Context::GenerateKeyEventParameters(Dictionary& parameters, Input::KeyIdentifier key_identifier) { parameters.Set("key_identifier", (int) key_identifier); } // Builds the parameters for a generic mouse event. void Context::GenerateMouseEventParameters(Dictionary& parameters, int button_index) { parameters.Set("mouse_x", mouse_position.x); parameters.Set("mouse_y", mouse_position.y); if (button_index >= 0) parameters.Set("button", button_index); } // Builds the parameters for the key modifier state. void Context::GenerateKeyModifierEventParameters(Dictionary& parameters, int key_modifier_state) { static String property_names[] = { "ctrl_key", "shift_key", "alt_key", "meta_key", "caps_lock_key", "num_lock_key", "scroll_lock_key" }; for (int i = 0; i < 7; i++) parameters.Set(property_names[i], (int) ((key_modifier_state & (1 << i)) > 0)); } // Builds the parameters for a drag event. void Context::GenerateDragEventParameters(Dictionary& parameters) { parameters.Set("drag_element", (void*) *drag); } // Releases all unloaded documents pending destruction. void Context::ReleaseUnloadedDocuments() { if (!unloaded_documents.empty()) { ElementList documents = unloaded_documents; unloaded_documents.clear(); // Clear the deleted list. for (size_t i = 0; i < documents.size(); ++i) documents[i]->GetEventDispatcher()->DetachAllEvents(); documents.clear(); } } // Sends the specified event to all elements in new_items that don't appear in old_items. void Context::SendEvents(const ElementSet& old_items, const ElementSet& new_items, const String& event, const Dictionary& parameters, bool interruptible) { ElementList elements; std::set_difference(old_items.begin(), old_items.end(), new_items.begin(), new_items.end(), std::back_inserter(elements)); std::for_each(elements.begin(), elements.end(), RKTEventFunctor(event, parameters, interruptible)); } void Context::OnReferenceDeactivate() { if (instancer != NULL) { instancer->ReleaseContext(this); } } } }