Paintbrush

Here is an example showing a more dynamic use of Wt's vector graphics API.

Wt's WPaintedWidget renders as SVG, VML or HTML5 graphics depending on the capabilities of the browser. The backend decides how to render the graphics, the application programmer has to draw his graphics using the available methods in the WPainter API. The drawing primitives include points, lines, arcs, cubic splines, text, etc.

The update() method of WPaintedWidget is called with the PaintUpdate rendering flag to update the canvas without clearing the previously painted contents (which is the default behavior).

Every mouse drag operation on the simple painting device is sent to the server, which in turn updates the canvas. You could imagine more interesting use cases, such as a multi-user white board, or interactive visualizations.

You can use a custom cursor image (pencil) by providing a cursor image to WCssDecorationStyle.

Example
source
#include <Wt/WCssDecorationStyle.h>
#include <Wt/WColor.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WEvent.h>
#include <Wt/WPainter.h>
#include <Wt/WPaintedWidget.h>
#include <Wt/WPainterPath.h>
#include <Wt/WPen.h>
#include <Wt/WPointF.h>
#include <Wt/WPushButton.h>
#include <Wt/WRectF.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#include <Wt/WToolBar.h>

class PaintBrush : public Wt::WPaintedWidget
{
public:
    PaintBrush(int width, int height)
        : WPaintedWidget()
    {
        resize(width, height);

        decorationStyle().setCursor("icons/pencil.cur", Wt::Cursor::Cross);

        mouseDragged().connect(this, &PaintBrush::mouseDrag);
        mouseWentDown().connect(this, &PaintBrush::mouseDown);
        touchStarted().connect(this, &PaintBrush::touchStart);
        touchMoved().connect(this, &PaintBrush::touchMove);
        touchMoved().preventDefaultAction();

        color_ = Wt::WColor(Wt::StandardColor::Black);
    }

    void clear() {
        update();
    }

    void setColor(const Wt::WColor& c) {
        color_ = c;
    }

protected:
    virtual void paintEvent(Wt::WPaintDevice *paintDevice) {
        Wt::WPainter painter(paintDevice);
        painter.setRenderHint(Wt::RenderHint::Antialiasing);

        Wt::WPen pen;
        pen.setWidth(3);
        pen.setColor(color_);
        pen.setCapStyle(Wt::PenCapStyle::Flat);
        pen.setJoinStyle(Wt::PenJoinStyle::Miter);
        painter.setPen(pen);
        painter.drawPath(path_);

        path_ = Wt::WPainterPath(path_.currentPosition());
    }

private:
    Wt::WPainterPath path_;
    Wt::WColor color_;

    void mouseDown(const Wt::WMouseEvent& e) {
        Wt::Coordinates c = e.widget();
        path_ = Wt::WPainterPath(Wt::WPointF(c.x, c.y));
    }

    void mouseDrag(const Wt::WMouseEvent& e) {
        Wt::Coordinates c = e.widget();
        path_.lineTo(c.x, c.y);
        update(Wt::PaintFlag::Update);
    }

    void touchStart(const Wt::WTouchEvent& e) {
        Wt::Coordinates c = e.touches()[0].widget();
        path_ = Wt::WPainterPath(Wt::WPointF(c.x, c.y));
    }

    void touchMove(const Wt::WTouchEvent& e) {
        Wt::Coordinates c = e.touches()[0].widget();
        path_.lineTo(c.x, c.y);
        update(Wt::PaintFlag::Update);
    }
};

namespace {


Wt::WPushButton *createColorToggle(const char *className, const Wt::WColor& color,
                                   PaintBrush *canvas)
{
    auto button = new Wt::WPushButton();
    button->setTextFormat(Wt::TextFormat::XHTML);
    button->setText("&nbsp;");
    button->setCheckable(true);
    button->addStyleClass(className);
    button->setWidth(30);
    button->checked().connect([=] {
        canvas->setColor(color);
    });

    return button;
}

}


/* Approximate bootstrap standard colors */
const Wt::WColor blue("#0d6efd");                              // btn-primary
const Wt::WColor red("#dc3545");                               // btn-danger
const Wt::WColor green("#198754");                             // btn-success
const Wt::WColor yellow("#ffc107");                            // btn-warning
const Wt::WColor black = Wt::WColor(Wt::StandardColor::Black); // btn-inverse
const Wt::WColor gray("#6c757d");                              // btn-secondary

auto result = std::make_unique<Wt::WContainerWidget>();

auto canvas = std::make_unique<PaintBrush>(710, 400);
auto canvas_ = canvas.get();
canvas->setColor(blue);
canvas->decorationStyle().setBorder
    (Wt::WBorder(Wt::BorderStyle::Solid, Wt::BorderWidth::Medium, black));

std::vector<Wt::WPushButton *> colorButtons {
  createColorToggle("btn-blue", blue, canvas.get()),
  createColorToggle("btn-danger", red, canvas.get()),
  createColorToggle("btn-success", green, canvas.get()),
  createColorToggle("btn-warning", yellow, canvas.get()),
  createColorToggle("btn-black", black, canvas.get()),
  createColorToggle("btn-secondary", gray, canvas.get())
};

auto toolBar = std::make_unique<Wt::WToolBar>();

for (unsigned i = 0; i < colorButtons.size(); ++i) {
    Wt::WPushButton *button = colorButtons[i];
    button->setChecked(i == 0);
    toolBar->addButton(std::unique_ptr<Wt::WPushButton>(button));

    // Implement a radio button group
        for (unsigned j = 0; j < colorButtons.size(); ++j) {
            if (i != j) {
                Wt::WPushButton * const other = colorButtons[j];
                button->checked().connect(other, &Wt::WPushButton::setUnChecked);
            }
        }
}

auto clearButton = std::make_unique<Wt::WPushButton>("Clear");

clearButton->clicked().connect([=] {
    canvas_->clear();
});

toolBar->addSeparator();
toolBar->addButton(std::move(clearButton));

result->addWidget(std::move(toolBar));
result->addWidget(std::move(canvas));

Top