Flags vs States
Understanding when to use flags versus states is the key to writing expressive, maintainable Malena components.
Flags vs States — The Core Difference
These two systems work together but serve different purposes. Knowing which to use makes your components cleaner and more expressive.
Flags — Independent Booleans
- Each flag is independent — any combination can be true at once
- No mutual exclusivity between flags
- No transition callbacks
- Best for: properties that can coexist (selected AND highlighted AND dragging)
- Read in
draw()to adjust visuals
States — Exclusive Active Mode
- Only one state is active at a time
- Transitions fire
onStateEnter/onStateExit - Models mutually exclusive modes
- Best for: modes where behavior changes meaningfully (Idle, Active, Dead)
- Drive state-specific animation in callbacks
When to Use Each
Can multiple properties be true simultaneously?
Yes → use flags. A button can be hovered AND selected AND highlighted at the same time. These are independent booleans.
Do properties exclude each other?
Yes → use a state. A character can only be Idle OR Running OR Jumping, never two at once. States enforce this mutual exclusivity automatically.
Do you need transition callbacks?
Yes → use states. onStateEnter / onStateExit let you react to transitions cleanly, without polling. Flags have no such callbacks.
Is it a simple binary on/off property?
Yes → use a flag. Draggable, hidden, enabled, completed — simple boolean properties that don't transition into each other.
Quick Mental Model
States = modes that exclude each other (idle, active, paused, dead)
If you could say "the card is selected and highlighted and pinned" → flags.
If you'd say "the enemy is either idle or chasing or attacking" → state.
class TaskCard : public ml::ComponentWith
{
// Manifest declares:
// enum class Flag { Selected, Highlighted, Completed, Pinned };
// enum class State { Idle, Active, Done };
//
// Flag::Selected/Highlighted/Completed can ALL be true simultaneously ✓
// State::Idle/Active/Done are mutually exclusive ✓
public:
void registerEvents()
{
// State machine: entering Done also sets Completed flag
onStateEnter([this](State s) {
if (s == State::Done) {
enableFlag(Flag::Completed); // flag set alongside state transition
fadeTo(200, 0.3f); // visual change driven by state
}
});
// Flags: hover and selected can combine freely
onHover([this] { enableFlag(Flag::Highlighted); });
onUnhover([this] { disableFlag(Flag::Highlighted); });
onClick([this]{
if (isState(State::Done))
setState(State::Active); // ← state machine transition
else
setState(State::Done);
});
}
void draw(sf::RenderTarget& t, sf::RenderStates s) const override
{
// State drives exclusive color
sf::Color bg;
switch (getState()) {
case State::Active: bg = sf::Color(50, 90, 50); break;
case State::Done: bg = sf::Color(30, 30, 30); break;
default: bg = sf::Color(38, 33, 92); break;
}
_body.setFillColor(bg);
// Flags drive overlay effects — can combine freely
if (checkFlag(Flag::Highlighted))
_body.setOutlineColor(sf::Color(255, 255, 255, 80));
if (checkFlag(ml::Flag::DRAGGING))
_body.setOutlineColor(sf::Color(93, 202, 165, 200));
t.draw(_body, s);
}
};
Common Mistakes to Avoid
// ❌ Three flags for mutually exclusive modes — can accidentally combine
class Enemy {
bool _isIdle = true;
bool _isChasing = false;
bool _isAttacking = false;
// Bug: both _isIdle and _isAttacking can be true at once
};
// ✅ State machine enforces mutual exclusivity
class Enemy : public ml::ComponentWith {
// enum class State { Idle, Chasing, Attacking };
// setState() makes impossible combinations impossible
};
// ❌ Combinatorial explosion — you'd need HoveredAndSelected, etc.
enum class BadState { Normal, Hovered, Selected, HoveredAndSelected, Disabled ... };
// ✅ Independent flags — any combination is naturally expressible
enum class Flag { Hovered, Selected, Disabled };
// Component can be Selected AND Disabled AND Hovered simultaneously