NazaraEngine/src/Nazara/Widgets/Canvas.cpp

353 lines
11 KiB
C++

// Copyright (C) 2022 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Widgets module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Widgets/Canvas.hpp>
#include <Nazara/Widgets/DefaultWidgetTheme.hpp>
#include <limits>
#include <Nazara/Widgets/Debug.hpp>
namespace Nz
{
Canvas::Canvas(entt::registry& registry, Nz::EventHandler& eventHandler, Nz::CursorControllerHandle cursorController, UInt32 renderMask, int initialRenderLayer) :
BaseWidget(std::make_shared<DefaultWidgetTheme>()),
m_cursorController(cursorController),
m_renderMask(renderMask),
m_keyboardOwner(InvalidCanvasIndex),
m_hoveredWidget(InvalidCanvasIndex),
m_mouseOwner(InvalidCanvasIndex),
m_registry(registry)
{
m_canvas = this;
BaseWidget::m_registry = &m_registry;
m_widgetParent = nullptr;
SetBaseRenderLayer(initialRenderLayer);
// Register ourselves as a widget to handle cursor change
RegisterToCanvas();
// Connect to every meaningful event
m_keyPressedSlot.Connect(eventHandler.OnKeyPressed, this, &Canvas::OnEventKeyPressed);
m_keyReleasedSlot.Connect(eventHandler.OnKeyReleased, this, &Canvas::OnEventKeyReleased);
m_mouseButtonPressedSlot.Connect(eventHandler.OnMouseButtonPressed, this, &Canvas::OnEventMouseButtonPressed);
m_mouseButtonReleasedSlot.Connect(eventHandler.OnMouseButtonReleased, this, &Canvas::OnEventMouseButtonRelease);
m_mouseEnteredSlot.Connect(eventHandler.OnMouseEntered, this, &Canvas::OnEventMouseEntered);
m_mouseLeftSlot.Connect(eventHandler.OnMouseLeft, this, &Canvas::OnEventMouseLeft);
m_mouseMovedSlot.Connect(eventHandler.OnMouseMoved, this, &Canvas::OnEventMouseMoved);
m_mouseWheelMovedSlot.Connect(eventHandler.OnMouseWheelMoved, this, &Canvas::OnEventMouseWheelMoved);
m_textEnteredSlot.Connect(eventHandler.OnTextEntered, this, &Canvas::OnEventTextEntered);
m_textEditedSlot.Connect(eventHandler.OnTextEdited, this, &Canvas::OnEventTextEdited);
}
std::size_t Canvas::RegisterWidget(BaseWidget* widget)
{
WidgetEntry box;
box.cursor = widget->GetCursor();
box.widget = widget;
std::size_t index = m_widgetEntries.size();
m_widgetEntries.emplace_back(box);
NotifyWidgetBoxUpdate(index);
return index;
}
void Canvas::UnregisterWidget(std::size_t index)
{
WidgetEntry& entry = m_widgetEntries[index];
if (m_hoveredWidget == index)
m_hoveredWidget = InvalidCanvasIndex;
if (m_mouseOwner == index)
{
m_mouseOwner = InvalidCanvasIndex;
m_mouseOwnerButtons.reset();
}
if (m_keyboardOwner == index)
m_keyboardOwner = InvalidCanvasIndex;
if (m_widgetEntries.size() > 1U)
{
WidgetEntry& lastEntry = m_widgetEntries.back();
std::size_t lastEntryIndex = m_widgetEntries.size() - 1;
entry = std::move(lastEntry);
entry.widget->UpdateCanvasIndex(index);
if (m_hoveredWidget == lastEntryIndex)
m_hoveredWidget = index;
if (m_mouseOwner == lastEntryIndex)
m_mouseOwner = index;
if (m_keyboardOwner == lastEntryIndex)
m_keyboardOwner = index;
}
m_widgetEntries.pop_back();
}
template<typename F>
void Canvas::DispatchEvent(std::size_t widgetIndex, F&& functor)
{
for (;;)
{
WidgetEntry& targetWidget = m_widgetEntries[widgetIndex];
if (functor(targetWidget))
return;
if (!targetWidget.widget->m_widgetParent)
return;
widgetIndex = targetWidget.widget->m_widgetParent->m_canvasIndex;
}
}
void Canvas::OnEventMouseButtonPressed(const EventHandler* /*eventHandler*/, const WindowEvent::MouseButtonEvent& event)
{
UpdateHoveredWidget(event.x, event.y);
if (std::size_t targetWidgetIndex = GetMouseEventTarget(); targetWidgetIndex != InvalidCanvasIndex)
{
DispatchEvent(targetWidgetIndex, [&](WidgetEntry& widgetEntry)
{
int x = static_cast<int>(std::round(event.x - widgetEntry.box.x));
int y = static_cast<int>(std::round(m_size.y - event.y - widgetEntry.box.y));
if (event.clickCount == 2)
return widgetEntry.widget->OnMouseButtonDoublePress(x, y, event.button);
else if (event.clickCount == 3)
return widgetEntry.widget->OnMouseButtonTriplePress(x, y, event.button);
else
return widgetEntry.widget->OnMouseButtonPress(x, y, event.button);
});
}
SetMouseOwner(m_hoveredWidget);
m_mouseOwnerButtons[event.button] = true;
}
void Canvas::OnEventMouseButtonRelease(const EventHandler* /*eventHandler*/, const WindowEvent::MouseButtonEvent& event)
{
if (std::size_t targetWidgetIndex = GetMouseEventTarget(); targetWidgetIndex != InvalidCanvasIndex)
{
DispatchEvent(targetWidgetIndex, [&](WidgetEntry& widgetEntry)
{
int x = static_cast<int>(std::round(event.x - widgetEntry.box.x));
int y = static_cast<int>(std::round(m_size.y - event.y - widgetEntry.box.y));
return widgetEntry.widget->OnMouseButtonRelease(x, y, event.button);
});
}
m_mouseOwnerButtons[event.button] = false;
if (m_mouseOwnerButtons.none())
SetMouseOwner(InvalidCanvasIndex);
UpdateHoveredWidget(event.x, event.y);
}
void Canvas::OnEventMouseEntered(const EventHandler* /*eventHandler*/)
{
// Keep previous mouse states but not new ones
if (m_mouseOwner != InvalidCanvasIndex)
{
WidgetEntry& ownerWidget = m_widgetEntries[m_mouseOwner];
for (std::size_t i = 0; i < Mouse::ButtonCount; ++i)
{
if (m_mouseOwnerButtons[i])
{
if (!Mouse::IsButtonPressed(static_cast<Mouse::Button>(i)))
{
ownerWidget.widget->OnMouseButtonRelease(-1, -1, static_cast<Mouse::Button>(i));
m_mouseOwnerButtons[i] = false;
}
}
}
if (m_mouseOwnerButtons.none())
SetMouseOwner(InvalidCanvasIndex);
}
else
m_mouseOwnerButtons.reset();
}
void Canvas::OnEventMouseLeft(const EventHandler* /*eventHandler*/)
{
if (std::size_t targetWidgetIndex = GetMouseEventTarget(); targetWidgetIndex != InvalidCanvasIndex)
{
m_widgetEntries[targetWidgetIndex].widget->OnMouseExit();
m_hoveredWidget = InvalidCanvasIndex;
}
}
void Canvas::OnEventMouseMoved(const EventHandler* /*eventHandler*/, const WindowEvent::MouseMoveEvent& event)
{
// Don't update hovered widget while the user doesn't release its mouse
UpdateHoveredWidget(event.x, event.y);
if (std::size_t targetWidgetIndex = GetMouseEventTarget(); targetWidgetIndex != InvalidCanvasIndex)
{
WidgetEntry& targetWidget = m_widgetEntries[targetWidgetIndex];
int x = static_cast<int>(std::round(event.x - targetWidget.box.x));
int y = static_cast<int>(std::round(m_size.y - event.y - targetWidget.box.y));
targetWidget.widget->OnMouseMoved(x, y, event.deltaX, -event.deltaY);
}
}
void Canvas::OnEventMouseWheelMoved(const EventHandler* /*eventHandler*/, const WindowEvent::MouseWheelEvent& event)
{
if (std::size_t targetWidgetIndex = GetMouseEventTarget(); targetWidgetIndex != InvalidCanvasIndex)
{
DispatchEvent(targetWidgetIndex, [&](WidgetEntry& widgetEntry)
{
int x = static_cast<int>(std::round(event.x - widgetEntry.box.x));
int y = static_cast<int>(std::round(m_size.y - event.y - widgetEntry.box.y));
return widgetEntry.widget->OnMouseWheelMoved(x, y, event.delta);
});
}
}
void Canvas::OnEventKeyPressed(const EventHandler* eventHandler, const WindowEvent::KeyEvent& event)
{
if (m_keyboardOwner != InvalidCanvasIndex)
{
if (m_widgetEntries[m_keyboardOwner].widget->OnKeyPressed(event))
return;
if (event.virtualKey == Keyboard::VKey::Tab)
{
if (!event.shift)
{
// Forward
for (std::size_t i = m_keyboardOwner + 1; i < m_widgetEntries.size(); ++i)
{
if (m_widgetEntries[i].widget->IsFocusable())
{
SetKeyboardOwner(i);
return;
}
}
for (std::size_t i = 0; i < m_keyboardOwner; ++i)
{
if (m_widgetEntries[i].widget->IsFocusable())
{
SetKeyboardOwner(i);
return;
}
}
}
else
{
// Backward
for (decltype(m_widgetEntries)::reverse_iterator rit{ m_widgetEntries.begin() + m_keyboardOwner }; rit != m_widgetEntries.rend(); ++rit)
{
if (rit->widget->IsFocusable())
{
SetKeyboardOwner(std::distance(m_widgetEntries.begin(), rit.base()) - 1);
return;
}
}
decltype(m_widgetEntries)::reverse_iterator rend { m_widgetEntries.begin() + m_keyboardOwner };
for (auto rit = m_widgetEntries.rbegin(); rit != rend; ++rit)
{
if (rit->widget->IsFocusable())
{
SetKeyboardOwner(std::distance(m_widgetEntries.begin(), rit.base()) - 1);
return;
}
}
}
}
}
OnUnhandledKeyPressed(eventHandler, event);
}
void Canvas::OnEventKeyReleased(const EventHandler* eventHandler, const WindowEvent::KeyEvent& event)
{
if (m_keyboardOwner != InvalidCanvasIndex)
m_widgetEntries[m_keyboardOwner].widget->OnKeyReleased(event);
OnUnhandledKeyReleased(eventHandler, event);
}
void Canvas::OnEventTextEntered(const EventHandler* /*eventHandler*/, const WindowEvent::TextEvent& event)
{
if (m_keyboardOwner != InvalidCanvasIndex)
m_widgetEntries[m_keyboardOwner].widget->OnTextEntered(event.character, event.repeated);
}
void Canvas::OnEventTextEdited(const EventHandler* /*eventHandler*/, const WindowEvent::EditEvent& event)
{
if (m_keyboardOwner != InvalidCanvasIndex)
m_widgetEntries[m_keyboardOwner].widget->OnTextEdited(event.text, event.length);
}
void Canvas::UpdateHoveredWidget(int x, int y)
{
std::size_t bestEntry = InvalidCanvasIndex;
float bestEntryArea = std::numeric_limits<float>::infinity();
int bestEntryLayer = std::numeric_limits<int>::min();
Vector3f mousePos(float(x), m_size.y - float(y), 0.f);
for (std::size_t i = 0; i < m_widgetEntries.size(); ++i)
{
const Boxf& box = m_widgetEntries[i].box;
int layer = m_widgetEntries[i].widget->GetBaseRenderLayer();
if (box.Contains(mousePos))
{
float area = box.width * box.height;
if (area < bestEntryArea && layer >= bestEntryLayer)
{
bestEntry = i;
bestEntryArea = area;
bestEntryLayer = layer;
}
}
}
// If we have a mouse owner, only allow to hover it or not
if (m_mouseOwner != InvalidCanvasIndex && bestEntry != m_mouseOwner)
bestEntry = InvalidCanvasIndex;
if (bestEntry != InvalidCanvasIndex)
{
if (m_hoveredWidget != bestEntry)
{
if (m_hoveredWidget != InvalidCanvasIndex)
{
WidgetEntry& previouslyHovered = m_widgetEntries[m_hoveredWidget];
previouslyHovered.widget->OnMouseExit();
}
m_hoveredWidget = bestEntry;
m_widgetEntries[m_hoveredWidget].widget->OnMouseEnter();
// Only allow cursor update when not owning mouse
if (m_cursorController && m_mouseOwner == InvalidCanvasIndex)
m_cursorController->UpdateCursor(Cursor::Get(m_widgetEntries[m_hoveredWidget].cursor));
}
}
else if (m_hoveredWidget != InvalidCanvasIndex)
{
m_widgetEntries[m_hoveredWidget].widget->OnMouseExit();
m_hoveredWidget = InvalidCanvasIndex;
// Only allow cursor update when not owning mouse
if (m_cursorController && m_mouseOwner == InvalidCanvasIndex)
m_cursorController->UpdateCursor(Cursor::Get(SystemCursor::Default));
}
}
}