Skip to main content

Prose

A read-only, theme-coloured surface for a document body.

Loading preview…
#include <QApplication>
#include <QFile>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QStandardItemModel>
#include <QUrl>
#include <QVBoxLayout>
#include <array>
#ifdef SHADCN_GALLERY_MEDIA
#include <shadcn/media.hpp>
#endif
#include <shadcn/shadcn.hpp>

QWidget* example(QWidget* parent = nullptr) {
using namespace shadcn;
auto* canvas = new QWidget(parent);
canvas->setAutoFillBackground(true);
auto* outer = new QHBoxLayout(canvas);
outer->setContentsMargins(32, 32, 32, 32);
auto* host = new QWidget(canvas);
auto* layout = new QVBoxLayout(host);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(16);
outer->addStretch();
outer->addWidget(host, 0, Qt::AlignCenter);
outer->addStretch();
// A lesson body: a heading, prose, a list, code and a link, so the type
// scale and the block spacing are both visible.
auto* prose = new Prose(host);
prose->setFixedSize(420, 320);
prose->setHtml(QStringLiteral(
"<h1>Lesson 4</h1>"
"<p>A system takes an input, does something to it, and produces an "
"output. The middle part is where the interesting decisions live, and "
"it is the part most systems are bad at describing.</p>"
"<h2>In this lesson</h2>"
"<ul><li>What an input and an output are</li>"
"<li>Why the middle is the hard part</li>"
"<li>One worked example</li></ul>"
"<p>The worked example, in short:</p>"
"<pre><code>signal -&gt; process -&gt; response</code></pre>"
"<p>Read <a href=\"#\">the notes for lesson 3</a> first if you have not.</p>"));
layout->addWidget(prose);
return canvas;
}

Installation​

add_subdirectory(shadcn-cpp)
target_link_libraries(app PRIVATE shadcn::widgets)

Usage​

A lesson is read, not typed into. Prose is a QTextEdit under the theme's typography: read only, selectable by mouse and by keyboard, focusable, and with no caret.

#include <shadcn/rows.hpp>

shadcn::Prose prose(parent);
prose.setHtml(QStringLiteral(
"<h1>Lesson 4</h1>"
"<p>A system takes an input and produces an output.</p>"
"<ul><li>What an input is</li><li>Why the middle is the hard part</li></ul>"
"<p>Read <a href=\"#\">the notes for lesson 3</a> first.</p>"));

The document structure is available to a caller that needs it rather than the text, which is what a table of contents or a progress indicator is built from.

prose.headings(); // { "Lesson 4", ... }
prose.headingLevels(); // { 1, 2, ... }

API​

MemberPurpose
applyTypography()Rebuilds the document stylesheet from the theme. Called on a theme or font change.
headings()The text of each heading block, in order.
headingLevels()The level of each heading block, in order.
readerMode()True when the surface cannot be typed into.
setReaderMode(bool)Turns editing on or off, keeping selection either way.

Notes​

The text layout is Qt's. A document engine is not this library's work, and reimplementing wrapping would lose selection, scrolling, and the accessibility a real text surface provides for free. The type scale and the colours are the theme's, and a theme change rebuilds the stylesheet without touching the document, so the text, the selection and the scroll position all survive.

The surface is the page, not a field. A text edit's own base colour is the platform's idea of a field, which is the one surface a reader never asked for.

List items get padding, which is the one property that moves Qt's list marker left and its text right together. Margin and text-indent move both the same way, leaving the gap at nothing, so a list reads as one run.