Byte Incrementer

Each component included in a library requires two classes to be defined: One class, implementing the Component interface, defines the behavior of an individual component; the other, implementing the ComponentFactory interface, defines behavior of the overall component and manufactures individual components. The relationship between objects of these two classes is much like the relationship between an object in Java and its class.

Directly implementing all the methods in the Component and ComponentFactory interfaces is rather tedious and repetitive. In practice, it is far more convenient to extend the ManagedComponent and AbstractComponentFactory classes instead.

Most of the classes relevant to defining component libraries is found in three libraries.

com.cburch.logisim.comp
Contains classes specifically related to defining components, including the Component, ComponentFactory, ManagedComponent, and AbstractComponentFactory types described above.

com.cburch.logisim.data
Contains classes related to data elements associated with components, such as the Location class for representing points on the canvas or the Value class for representing values that can exist on a wire.

com.cburch.logisim.tools
Contains classes related to defining tools and specifying interaction between components and tools. (This is only necessary for the more specialized components.)

ByteIncrementer

This is a minimal example illustrating the essential elements to defining a component. This particular component is an incrementer, which takes an 8-bit input and produces an 8-bit output whose value is one more than its input.

This example by itself is not enough to create a working JAR file; you must also provide a Library class, as illustrated in the next section of this guide.

package com.cburch.incr;

import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.comp.ComponentFactory;
import com.cburch.logisim.comp.EndData;
import com.cburch.logisim.comp.ManagedComponent;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;

/** Implements a bare-bones custom Logisim component, whose single
 * input is 8 bits wide, and whose output value is one more than
 * the input value, also 8 bits wide. */
class ByteIncrementer extends ManagedComponent {
    // The ManagedComponent class conveniently implements just about
    // all of the required methods. All we have to do is to tell it
    // about the different "ends" (i.e., inputs and outputs to the
    // component) in the constructor, and we need to implement the
    // getFactory, propagate, and draw.
    
    /** The width of a byte. */
    private static final BitWidth BIT_WIDTH = BitWidth.create(8);
    
    /** Constructs a component at the given location, with the given
     * attributes. */
    ByteIncrementer(Location loc, AttributeSet attrs) {
        super(loc, attrs, 2);
        // The third parameter (2) to the parent's constructor indicates how
        // many ends the component has. It's not important that this be
        // precisely right: The ManagedComponent uses an ArrayList to manage
        // the ends.
        
        // Now we tell the ManagedComponent superclass about the ends. We
        // assign each end a distinct index, which is used also below in
        // the propagate method.
        setEnd(0, loc.translate(-30, 0), BIT_WIDTH, EndData.INPUT_ONLY);
        setEnd(1, loc,                   BIT_WIDTH, EndData.OUTPUT_ONLY);
    }
    
    /** Returns the class that generated this component. */
    public ComponentFactory getFactory() {
        return ByteIncrementerFactory.instance;
    }

    /** Recomputes the outputs of this component. The circuitState
     * parameter maintains information about the current state of the
     * circuit. */
    public void propagate(CircuitState circuitState) {
        // Retrieve the current value coming into this component.
        Value in = circuitState.getValue(getEndLocation(0));
        
        // Compute the output.
        Value out;
        if(in.isFullyDefined()) {
            // If all input bits are 0 or 1, our input is a valid number, and
            // so can be our output.
            out = Value.createKnown(BIT_WIDTH, in.toIntValue() + 1);
        } else if(in.isErrorValue()) {
            // If any input bits are "errors" (which usually arise from
            // conflicting values on a wire), then we send out all error bits.
            out = Value.createError(BIT_WIDTH);
        } else {
            // Otherwise, some input bits are unspecified. To keep things
            // simple, we'll indicate that all output bits are also unspecified.
            out = Value.createUnknown(BIT_WIDTH);
        }
        
        // Now propagate the output into the circuit state. The parameters
        // here indicate the location affected, the value sent there, the
        // originating component, and the delay. The delay needs to be positive,
        // and it should bear some resemblance to the component's depth, but
        // the exact value isn't too important. The incrementing component
        // would probably be nine levels deep, so I use that value here.
        circuitState.setValue(getEndLocation(1), out, this, 9);
    }

    /** Draws this component using the data contained in the parameter. */
    public void draw(ComponentDrawContext context) {
        // The ComponentDrawContext class contains several convenience
        // methods for common operations. I've kept the drawing simple
        // by just sticking to these operations.
        context.drawRectangle(this, "+1");
        context.drawPins(this);
    }
}

ByteIncrementerFactory

package com.cburch.incr;

import com.cburch.logisim.comp.AbstractComponentFactory;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;

/** The object that manufactures ByteIncrementers. */
class ByteIncrementerFactory extends AbstractComponentFactory {
    // The AbstractComponentFactory parent class conveniently implements
    // just about all the methods we need for ComponentFactory. All we really
    // need are the getName, createComponent, and getOffsetBounds methods
    // here.
    
    /** The sole instance of this class. */
    static final ByteIncrementerFactory instance = new ByteIncrementerFactory();
    
    /** Constructs an instance. There is no reason to have multiple instances
     * of this class, so I make the constructor method private to restrict creation
     * to within this class only. */
    private ByteIncrementerFactory() { }
    
    /** Returns the name of this component class, as it is stored in a file. */
    public String getName() {
        return "Byte Incrementer";
    }

    /** Returns the name of this component class as the user should see it. */
    public String getDisplayName() {
        // This may well be different from what is returned by getName.
        // The two most likely reasons for having different strings are
        // that we decide on a more user-friendly name in a future version
        // but we don't want to change the representation within files (for
        // backwards compatibility), or that we want to adapt to the user's
        // chosen language. (Logisim doesn't support internationalization
        // right now, but it is capable of doing so.)
        return "Incrementer (8-Bit)";
    }

    /** Manufactures and returns a component of this component class. */
    public Component createComponent(Location loc, AttributeSet attrs) {
        return new ByteIncrementer(loc, attrs);
    }

    /** Returns a rectangle indicating where the component would appear
     * if it were dropped at the origin. */
    public Bounds getOffsetBounds(AttributeSet attrs) {
        // In this case, the component is a 30x30 rectangle, with the
        // origin on the midpoint of the east side. So the x-coordinate
        // of the top left corner is -30, the y-coordinate is -15, and
        // of course the width and height are both 30.
        return Bounds.create(-30, -15, 30, 30);
    }
    
    // We could customize the icon associated with the tool by overriding
    // the paintIcon method here.

    // We could also override the drawGhost method to customize the appearance
    // of the "ghost" drawn as the user moves the tool across the canvas. By
    // default, the ghost is a rectangle corresponding to getOffsetBounds. A
    // ByteIncrementer is just such a rectangle, so there's no need to override.
}

Next: Library Class.