Skip to content

Element System

Rodrigo Torres edited this page Apr 23, 2026 · 1 revision

This is the most important subsystem of the project. All circuit components (logic gates, flip-flops, displays, etc.) are elements. Their code lives in App/Element/.


Class Hierarchy

QGraphicsObject
  └── GraphicElement              (abstract base — all circuit components)
        ├── GraphicElementInput   (abstract — user-interactive inputs)
        │     ├── InputSwitch     (toggle on/off)
        │     ├── InputButton     (momentary press)
        │     ├── InputRotary     (multi-position rotary switch)
        │     └── Clock           (frequency-driven square wave)
        ├── InputVcc              (constant HIGH — inherits GraphicElement directly)
        ├── InputGnd              (constant LOW — inherits GraphicElement directly)
        ├── And, Or, Not, ...     (combinational logic gates)
        ├── DFlipFlop, SRLatch... (sequential logic)
        ├── Led, Buzzer, Display  (output elements)
        ├── Mux, Demux            (multiplexing)
        ├── IC                    (sub-circuit / integrated circuit)
        └── Node, Line, Text      (structural/annotation)

GraphicElement: The Base Class

App/Element/GraphicElement.h is the abstract base class for all graphic elements in the circuit. It inherits from:

  • QGraphicsObject: visual representation in the Qt scene
  • ItemWithId: stable numeric identity (for serialization and undo/redo)

Responsibilities:

  • Port management (input and output)
  • Rendering with pixmaps/skins
  • Serialization to QDataStream (save/load)
  • Grid-snapping (alignment to grid)
  • Label display and keyboard triggers
  • Selection highlighting

Key methods that subclasses implement:

// Simulation logic — REQUIRED for functional elements
virtual void updateLogic() = 0;

// Custom serialization (optional)
virtual void save(QDataStream &stream) const;
virtual void load(QDataStream &stream, SerializationContext &context);

ElementFactory

App/Element/ElementFactory.h is a singleton that creates elements from their ElementType:

// Create a new AND element
GraphicElement *andGate = ElementFactory::buildElement(ElementType::And);

// Check if a type is registered
bool exists = ElementFactory::hasCreator(ElementType::And);

// Type <-> string conversion
ElementType type = ElementFactory::textToType("And");
QString name = ElementFactory::typeToText(ElementType::And);

The factory does not manage IDs — that is the Scene's responsibility.


How Elements Register Themselves

wiRedPanda uses a self-registering template pattern. Each element specializes ElementInfo<T>, which:

  1. Defines compile-time ElementConstraints (input/output count, group, features).
  2. Provides a metadata() function returning display info (icon path, translated name).
  3. Contains a static inline const bool registered lambda that runs at program startup to register the element with both ElementMetadataRegistry and ElementFactory.

This means: no manual factory registration calls. Just defining the ElementInfo<T> specialization in the .cpp file is enough to make the element available everywhere — in the palette, in serialization, and in tests.

The ElementConstraints are validated at compile time by the constexpr validate() function. A misconfigured element will not compile.


ElementMetadata and ElementConstraints

ElementConstraints defines the restrictions for an element:

Field Description
type Element type (ElementType)
group Palette group (ElementGroup)
minInputSize Minimum number of input ports
maxInputSize Maximum number of input ports
minOutputSize Minimum number of output ports
maxOutputSize Maximum number of output ports
canChangeAppearance Allows the user to assign a custom SVG appearance
hasColors Has color option (e.g., LED)
hasAudio Produces sound (e.g., Buzzer)
hasAudioBox Has audio box capability (e.g., AudioBox)
hasTrigger Can be triggered via keyboard
hasFrequency Configurable frequency (e.g., Clock)
hasDelay Has configurable delay
hasLabel Displays a text label
hasTruthTable Based on a truth table
hasVolume Has volume control (e.g., Buzzer, AudioBox)
rotatable Can be rotated

ElementMetadata extends this with visual information: pixmap paths, translated names, and defaultAppearances/alternativeAppearances lists.


Element Catalog

The 31 concrete elements live in App/Element/GraphicElements/:

Logic Gates (combinational)

Element File Inputs Description
AND And.cpp 2-8 Output 1 if all inputs are 1
OR Or.cpp 2-8 Output 1 if any input is 1
NOT Not.cpp 1 Inverts the signal
NAND Nand.cpp 2-8 Inverted AND
NOR Nor.cpp 2-8 Inverted OR
XOR Xor.cpp 2-8 Exclusive OR
XNOR Xnor.cpp 2-8 Inverted exclusive OR

Sequential Elements (memory)

Element File Description
D Flip-Flop DFlipFlop.cpp Stores data on clock edge
JK Flip-Flop JKFlipFlop.cpp Versatile flip-flop with J, K, clk
SR Flip-Flop SRFlipFlop.cpp Set-Reset with clock edge
T Flip-Flop TFlipFlop.cpp Toggle on clock edge
D Latch DLatch.cpp Level-sensitive latch
SR Latch SRLatch.cpp Level-sensitive set-reset

Inputs

Element File Description
InputButton InputButton.cpp Momentary button (active while pressed)
InputSwitch InputSwitch.cpp Toggle switch (on/off)
InputRotary InputRotary.cpp Rotary switch with N positions
InputVCC InputVCC.cpp Constant HIGH source (1)
InputGND InputGND.cpp Constant LOW source (0)

Outputs

Element File Description
Led Led.cpp LED indicator (with color option)
Display7 Display7.cpp 7-segment display
Display14 Display14.cpp 14-segment display (alphanumeric)
Display16 Display16.cpp 16-segment display
Buzzer Buzzer.cpp Audio buzzer with configurable freq
AudioBox AudioBox.cpp Audio player

Multiplexing

Element File Description
Mux Mux.cpp Multiplexer: selects 1 of N inputs
Demux Demux.cpp Demultiplexer: routes input to 1 of N

Utilities

Element File Description
Clock Clock.cpp Clock generator with adjustable freq
Node Node.cpp Wire junction / wireless node (Tx/Rx)
Line Line.cpp Decorative line (no logic function)
Text Text.cpp Text annotation
TruthTable TruthTable.cpp Element defined by a truth table

Anatomy of a Logic Gate

Here is the complete AND gate, the simplest element to understand:

App/Element/GraphicElements/And.h:

class And : public GraphicElement {
    Q_OBJECT
public:
    explicit And(QGraphicsItem *parent = nullptr);
    void updateLogic() override;
};

App/Element/GraphicElements/And.cpp:

#include "And.h"

// 1. Registration: define constraints and metadata
template<>
struct ElementInfo<And> {
    static constexpr ElementConstraints constraints{
        .type = ElementType::And,
        .group = ElementGroup::Gate,
        .minInputSize = 2, .maxInputSize = 8,
        .minOutputSize = 1, .maxOutputSize = 1,
        .canChangeAppearance = true,
    };
    static_assert(validate(constraints));

    static ElementMetadata metadata() {
        auto meta = metadataFromConstraints(constraints);
        meta.pixmapPath = [] { return QStringLiteral(":/Components/Logic/and.svg"); };
        meta.titleText = QT_TRANSLATE_NOOP("And", "AND");
        meta.translatedName = QT_TRANSLATE_NOOP("And", "And");
        meta.trContext = "And";
        meta.defaultAppearances = {":/Components/Logic/and.svg"};
        return meta;
    }

    static inline const bool registered = [] {
        ElementMetadataRegistry::registerMetadata(metadata());
        ElementFactory::registerCreator(constraints.type, [] { return new And; });
        return true;
    }();
};

// 2. Constructor: just calls parent with the element type
And::And(QGraphicsItem *parent) : GraphicElement(ElementType::And, parent) {}

// 3. Logic: the one method that matters
void And::updateLogic() {
    if (!simUpdateInputsAllowUnknown()) return;
    setOutputValue(StatusOps::statusAndAll(simInputs()));
}

What happens in updateLogic():

  1. simUpdateInputsAllowUnknown() reads the current value from each connected predecessor's output port (or uses the port's default if unconnected). Returns false if a required input has no value — the element does nothing this cycle.
  2. simInputs() returns a QVector<Status> snapshot of all input values.
  3. StatusOps::statusAndAll() ANDs all inputs together using the four-state logic rules.
  4. setOutputValue() writes the result to output port 0. If the value changed, the element is flagged for downstream propagation.

All other combinational gates (Or, Not, Xor, Nand, Nor, Xnor) follow this exact same three-part pattern. The only difference is the StatusOps function called.


Anatomy of a Sequential Element

Sequential elements like flip-flops store internal state and respond to clock edges:

D Flip-Flop (DFlipFlop.cpp, simplified):

void DFlipFlop::updateLogic() {
    if (!simUpdateInputs()) return;          // Strict: needs ALL inputs valid

    Status q0 = simOutputs().at(0);          // Current Q
    Status q1 = simOutputs().at(1);          // Current ~Q
    const Status D    = simInputs().at(0);   // Data input
    const Status clk  = simInputs().at(1);   // Clock input
    const Status prst = simInputs().at(2);   // Async preset (active-low)
    const Status clr  = simInputs().at(3);   // Async clear (active-low)

    // Rising edge detection
    if (clk == Status::Active && m_simLastClk == Status::Inactive) {
        q0 = m_simLastValue;                 // Latch D on rising edge
        q1 = (m_simLastValue == Status::Active) ? Status::Inactive : Status::Active;
    }

    // Asynchronous overrides (active-low, jointly evaluated)
    if (prst == Status::Inactive || clr == Status::Inactive) {
        q0 = (prst == Status::Inactive) ? Status::Active : Status::Inactive;
        q1 = (clr == Status::Inactive) ? Status::Active : Status::Inactive;
    }

    m_simLastClk = clk;
    m_simLastValue = D;
    setOutputValue(0, q0);
    setOutputValue(1, q1);
}

Key differences from combinational gates:

  • Uses simUpdateInputs() (strict) instead of simUpdateInputsAllowUnknown().
  • Stores m_simLastClk and m_simLastValue across cycles for edge detection.
  • Has custom port layout via updatePortsProperties() (Clock pin at bottom-left, Preset at top, etc.).

Anatomy of an Input Element

Input elements like InputSwitch are driven by user interaction, not by upstream logic:

// No updateLogic() — state is set by mouse events
void InputSwitch::mousePressEvent(QGraphicsSceneMouseEvent *event) {
    if (!m_locked && event->button() == Qt::LeftButton) {
        setOn(!m_isOn);   // Toggle the switch
        event->accept();
    }
    QGraphicsItem::mousePressEvent(event);
}

// setOn(bool, int) is inherited from GraphicElementInput:
void GraphicElementInput::setOn(const bool value, const int port) {
    m_isOn = value;
    setPixmap(static_cast<int>(m_isOn));                     // Visual update
    outputPort()->setStatus(static_cast<Status>(m_isOn));    // Electrical update
}

The Clock element is special: it is driven by wall-clock time via updateClock(), comparing elapsed time against a configurable frequency interval.


Integrated Circuits (ICs)

The IC system allows reusing circuits as sub-components:

  • IC.cpp/h: the element representing an IC in the scene (a "black box" with ports)
  • ICRegistry.cpp/h: central registry that monitors .panda files and auto-updates ICs when the file changes

ICs support arbitrary hierarchy — an IC can contain other ICs inside it. The tests include up to 9 levels of complexity, culminating in complete CPU implementations.


See Also

Clone this wiki locally