ml::docs | Your First App Manifests Events Plugins Scenes Graphics Resources
← 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().

EventFires whenCategory
ml::Event::CLICKMouse button released over component boundsmouse
ml::Event::HOVERMouse cursor entered component boundsmouse
ml::Event::UNHOVERMouse cursor left component boundsmouse
ml::Event::MOUSE_PRESSEDRaw mouse press (before click logic)mouse
ml::Event::MOUSE_RELEASEDRaw mouse releasemouse
ml::Event::DRAGComponent is being dragged (needs DRAGGABLE flag)mouse
ml::Event::SCROLLMouse wheel scrolled over componentscroll
ml::Event::FOCUSComponent gained keyboard focusfocus
ml::Event::BLURComponent lost keyboard focusfocus
ml::Event::KEYPRESSKey pressed while component has focuskeyboard
ml::Event::KEY_RELEASEKey released while focusedkeyboard
ml::Event::TEXT_ENTEREDUnicode character entered while focusedkeyboard
ml::Event::UPDATEEvery frame, before drawingframe
ml::Event::WINDOW_RESIZEDApplication window was resizedwindow
ml::Event::WINDOW_FOCUS_GAINEDApp window gained OS focuswindow
ml::Event::WINDOW_FOCUS_LOSTApp 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);
    }
});
Use onTextEntered for text fields — it handles unicode, shift, and modifier keys correctly. Use onKeypress 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});
});

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 registerEvents() 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)