ml::docs | Your First App Manifests Events Messaging Plugins Scenes Resources
See all tutorials →
Beginner 7 sections ~20 min C++17/20

Built-in Events

Every Malena component inherits a full event system automatically. No setup — just call the method and respond to what happens.

Before you start

You'll need Malena and SFML set up. Check the quick start guide if you haven't.

Introduction

How the Event System Works

Every ml::Component inherits a full set of event traits automatically through ml::Core. Each trait exposes friendly callback methods. There is nothing to register or configure — just call the method on your component and provide a lambda.

The simplest possible event wiring cpp
void MyScreen::registerEvents()
{
    _button.onClick([this]{ handleClick(); });
    _button.onHover([this]{ _button.setFillColor(sf::Color::Yellow); });
    _panel.onUpdate([this]{ animate(); });
}
All callback methods have two overloads — one with no arguments for simple cases, and one that receives the raw const std::optional<sf::Event>& when you need details like key codes or mouse position.

1Core Concepts

The ml::Event Enum

All built-in events are identified by the ml::Event enum. You typically never use this directly — the trait methods handle it. It becomes useful with unsubscribe().

EventCallback methodFires whenCategory
ml::Event::CLICKonClick()Mouse button released over component boundsmouse
ml::Event::HOVERonHover()Mouse cursor entered component boundsmouse
ml::Event::UNHOVERonUnhover()Mouse cursor left component boundsmouse
ml::Event::MOUSE_PRESSEDonMousePressed()Raw mouse press (before click logic)mouse
ml::Event::MOUSE_RELEASEDonMouseReleased()Raw mouse releasemouse
ml::Event::DRAG— (enable via ml::Flag::DRAGGABLE)Component is being draggedmouse
ml::Event::SCROLLonScroll()Mouse wheel scrolled over componentscroll
ml::Event::FOCUSonFocus()Component gained keyboard focusfocus
ml::Event::BLURonBlur()Component lost keyboard focusfocus
ml::Event::KEYPRESSonKeypress()Key pressed while component has focuskeyboard
ml::Event::KEY_RELEASEonKeyRelease()Key released while focusedkeyboard
ml::Event::TEXT_ENTEREDonTextEntered()Unicode character entered while focusedkeyboard
ml::Event::UPDATEonUpdate()Every frame, before drawingframe
ml::Event::WINDOW_RESIZEDonWindowResized()Application window was resizedwindow
ml::Event::WINDOW_FOCUS_GAINEDonWindowFocusGained()App window gained OS focuswindow
ml::Event::WINDOW_FOCUS_LOSTonWindowFocusLost()App window lost OS focuswindow

2Core Concepts

Clickable and Hoverable

onClick and onHover cpp
// Simple callback — no event data needed
_submitBtn.onClick([this]{ submitForm(); });

// With event data — check which mouse button
_canvas.onClick([this](const std::optional& e) {
    if (!e) return;
    if (auto* mb = e->getIf())
        handleClick(mb->position);
});

// Hover — highlight on enter, restore on leave
_card.onHover([this]  { _card.setFillColor(sf::Color(60, 60, 80)); });
_card.onUnhover([this]{ _card.setFillColor(sf::Color(40, 40, 55)); });

// Focus — show cursor ring on text input
_textInput.onFocus([this]{ _textInput.setOutlineColor(sf::Color(124, 106, 247)); });
_textInput.onBlur([this] { _textInput.setOutlineColor(sf::Color(60, 60, 80)); });

3Core Concepts

Keyable — Keyboard Input

Keyboard events only fire on the currently focused component. A component must be clicked (or have focus explicitly given) before it receives keyboard input.

onKeypress and onTextEntered cpp
// Respond to specific keys (use for game controls / shortcuts)
_textInput.onKeypress([this](const std::optional& e) {
    if (!e) return;
    if (auto* kp = e->getIf()) {
        if (kp->code == sf::Keyboard::Key::Enter)   submitInput();
        if (kp->code == sf::Keyboard::Key::Escape)  clearInput();
    }
});

// Handle text input — unicode-safe, handles IME and modifiers correctly
// Prefer this over onKeypress for any text field
_textInput.onTextEntered([this](const std::optional& e) {
    if (!e) return;
    if (auto* te = e->getIf()) {
        if (te->unicode >= 32 && te->unicode < 127)
            _text += static_cast(te->unicode);
    }
});

// Key released — useful for "hold to aim, release to fire" patterns
_crosshair.onKeyRelease([this](const std::optional& e) {
    if (!e) return;
    if (auto* kr = e->getIf()) {
        if (kr->code == sf::Keyboard::Key::Space) fire();
    }
});
Use onTextEntered for text fields. Use onKeypress / onKeyRelease for game controls where you need raw key codes.

4Core Concepts

Updatable and Scrollable

onUpdate, onScroll, and window events cpp
// Per-frame animation — fires every frame before drawing
float _angle = 0.f;
_spinner.onUpdate([this]{
    _angle += 2.f;
    _spinner.setRotation(sf::degrees(_angle));
});

// Countdown timer
sf::Clock _clock;
_timer.onUpdate([this]{
    float remaining = 30.f - _clock.getElapsedTime().asSeconds();
    if (remaining <= 0.f) { onTimeUp(); return; }
    _timerText.setString(std::to_string((int)remaining));
});

// Mouse wheel scroll
_list.onScroll([this](const std::optional& e) {
    if (!e) return;
    if (auto* scroll = e->getIf()) {
        _offsetY -= scroll->delta * 30.f;
        _offsetY = std::clamp(_offsetY, 0.f, _maxScroll);
        updateLayout();
    }
});

// Reflow on window resize — no SFML event switch needed
_panel.onWindowResized([this]{
    auto size = ml::WindowManager::getWindow().getSize();
    _panel.setSize({(float)size.x, (float)size.y});
});

// Raw mouse events — fire on all components regardless of hover state
_canvas.onMouseMoved([this](const std::optional& e) {
    if (!e) return;
    if (auto* mm = e->getIf())
        updateCursor(mm->position.x, mm->position.y);
});

_canvas.onMousePressed([this](const std::optional& e) {
    if (!e) return;
    if (auto* mb = e->getIf())
        startDraw(mb->position.x, mb->position.y);
});

_canvas.onMouseReleased([this](const std::optional& e) {
    if (!e) return;
    if (auto* mb = e->getIf())
        finishDraw(mb->position.x, mb->position.y);
});
onMouseMoved, onMousePressed, and onMouseReleased fire on all registered components regardless of hover state. onClick fires only when press and release both occur inside the component's bounds.

5Core Concepts

Draggable — Mouse Drag

Dragging is enabled via a flag, not a callback. Set ml::Flag::DRAGGABLE and the framework handles all mouse tracking and position updating automatically.

Enabling and configuring drag cpp
// Basic free drag
_panel.setFlag(ml::Flag::DRAGGABLE);

// Drag on Y axis only (e.g. a vertical slider thumb)
_thumb.setFlag(ml::Flag::DRAGGABLE);
_thumb.setState(ml::DraggableManifest::State::LOCK_X);

// Constrain to a track area
_thumb.setDragBounds(sf::FloatRect{{trackX, trackY}, {trackWidth, trackHeight}});

// Revert to free drag
_thumb.clearDragBounds();
_thumb.setState(ml::DraggableManifest::State::FREE);

// Check if dragging in draw() to show visual feedback
void draw(sf::RenderTarget& t, sf::RenderStates s) const override
{
    if (checkFlag(ml::Flag::DRAGGING))
        _body.setOutlineColor(sf::Color(93, 202, 165, 200));
    t.draw(_body, s);
}

6Core Concepts

Custom Events — subscribe and publish

Declare an Event enum in your manifest and use subscribe / publish for components to communicate with each other — completely decoupled.

subscribe and publish cpp
// Declare in manifest:
// enum class Event { ScoreChanged, GameOver, PlayerDied };

class HUD : public ml::Component
{
    void onReady() override
    {
        subscribe(GameManifest::Event::ScoreChanged, [this]{
            updateScoreDisplay();
        });
        subscribe(GameManifest::Event::GameOver, [this]{
            showGameOverScreen();
        });
    }
};

class GameLogic : public ml::Component
{
    void onEnemyDefeated()
    {
        _score += 100;
        publish(GameManifest::Event::ScoreChanged);  // HUD updates immediately

        if (_score >= _targetScore)
            publish(GameManifest::Event::GameOver);
    }
};

// Unsubscribe a specific event
myRect.unsubscribe(ml::Event::CLICK);
myRect.unsubscribeAll();  // remove everything (also called automatically on destroy)