API — ICoreEssentials/UI/Backends/Gtk4/Widgets
The public contract of 12 header(s) under ICoreEssentials/UI/Backends/Gtk4/Widgets — 14 class/struct definition(s), 69 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreGtk4Accessibility.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4Accessibility.h
The accessibility floor for the painted lane on GTK4.
⚠ THE ROW IS NAMED IN THE .cpp AND THE BOARD'S FILENAME APPEARS IN NEITHER. This header briefly carried "L7.5 of <the board>" and the sentence saying not to was on the same line -- which is the zone README's own trap, and it cost a peer a red D15 (audience-leak) one docs rebuild later, in their commit rather than in the one that wrote it. A generated API page lifts these banners, so a product reader is handed an instruction to open a file they do not have.
⚠ NOTHING ABOVE src/ICoreEssentials/UI/Backends/Gtk4/ MAY INCLUDE THIS. It names GTK in a header, which is what the architecture census row R2.4 refuses everywhere else in this tree.
⚠⚠ WHY THIS FILE EXISTS AT ALL, IN ONE PARAGRAPH
File-scope declarations#
// The roles this product actually has. Deliberately NOT a re-export of
// GtkAccessibleRole: a caller in a seat should be choosing from the shapes this
// tree contains, and a 60-value enum invites picking a role no painted control
// here means. Each maps to exactly one GTK role in the .cpp.
enum class ICoreGtk4AccessibleRole {
// ⚠⚠ A LEAF WITH NOTHING TO SAY, AND NEVER A CONTAINER. Measurement 7:
// this deletes the node and everything under it. An empty ICoreLabel is
// the right use; the area around L2.3's GtkText is NOT, and choosing it
// there -- which is what "do not read the field twice" argues for -- would
// have removed the field from the tree instead of tidying it.
Presentation,
// The DEFAULT of every painted widget here, and a name on it is dropped
// (measurement 6). Naming something generic is a no-op, not a floor.
Generic,
Group, // a container that groups related controls
Button,
CheckBox,
Radio,
Switch,
Label,
TextBox,
SearchBox,
SpinButton,
Slider,
ProgressBar,
ScrollBar,
Separator,
ToolBar,
List,
ListItem,
TreeGrid,
Row,
Tab,
TabList,
TabPanel,
Menu,
MenuBar,
MenuItem,
ComboBox,
Document,
Image,
Dialog,
Window,
};
// A checkable thing is three-valued on this API and two-valued in most of this
// product; Mixed exists because GTK's own tristate does and a partially
// checked group is expressible.
//
// ⚠ Undefined IS A FOURTH VALUE AND GTK'S TRISTATE HAS THREE, deliberately.
// "This control is not checkable" and "this control is checkable and is not
enum class ICoreGtk4AccessibleTristate { False, True, Mixed, Undefined };
ICoreGtk4ButtonPainter.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4ButtonPainter.h
The GTK4 backend's branded button: the half of this backend's button seat that draws.
IT DECIDES NOTHING AND INVENTS NOTHING. Which body a button wears, whether it lays a resting plate down, how far the pointer has moved it and which colour each part lands in are all
UI/Portable/ICoreButtonChrome's -- product decisions, proved with no toolkit linked. What is here is putting ink on the numbers that unit returns, in the order the Qt widget puts them down.⚠ THE PAINTER IS
ICorePainter, NOT A BACKEND ONE, exactly as this zone's splitter and scroll-bar painters take. The WinUI peer takesICoreWinUIPainter&because reaching ICorePainter would pull the value tier -- and, through ICoreString, Qt -- into a suite that has to run with neither; on this backend that cost does not exist, because the widget seat's paint
Declares no class of its own — see the file.
ICoreGtk4ItemDelegateAccess.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4ItemDelegateAccess.h
ICoreItemDelegate's toolkit half on this backend, as an IMPL-SIDE header.
⚠ This is not part of the public surface and must not be included from one. It exists because TWO kinds of file in this zone need the Impl complete: the delegate's own seat DEFINES it, and every item VIEW has to call its three hooks while painting a row. Same precedent and same reasoning as UI/Backends/Qt/Widgets/ICoreStandardItemImpl.h and this zone's own Values/ICoreGtk4PixmapAccess.h -- the access lives in an impl-side header rather than in the zone's shared native-handle header, because nothing outside these files needs it.
A nested class may be DEFINED outside its enclosing class as long as it was DECLARED inside, which is what
class Impl;in ICoreItemDelegate.h does. Being nested is also what makes this file possible at all: the three hooks
ICoreItemDelegateHooks#
ICoreGtk4ItemDelegateAccess.h:51 · class · 4 declaration(s)
⚠ WHAT A VIEW ACTUALLY HOLDS, AND WHY IT IS THIS RATHER THAN THE Impl.
class ICoreItemDelegateHooks {
public:
virtual ~ICoreItemDelegateHooks() = default;
// The three hooks, forwarded. Non-const because the hooks are: an override
// is allowed to keep a per-paint cache.
virtual bool paint(ICorePainter& painter, const ICoreItemRenderContext& context) = 0;
virtual ICoreSizeF sizeHint(const ICoreItemRenderContext& context) = 0;
virtual ICoreColor selectedTextColorFor(const ICoreItemRenderContext& context) = 0;
};
};
ICoreItemDelegate#
ICoreGtk4ItemDelegateAccess.h:63 · class · bases :Impl : public ICoreItemDelegateHooks · 4 declaration(s)
class ICoreItemDelegate : :Impl : public ICoreItemDelegateHooks {
public:
explicit Impl(ICoreItemDelegate& owner);
bool paint(ICorePainter& painter, const ICoreItemRenderContext& context) override;
ICoreSizeF sizeHint(const ICoreItemRenderContext& context) override;
ICoreColor selectedTextColorFor(const ICoreItemRenderContext& context) override;
ICoreItemDelegate* m_owner;
};
};
ICoreGtk4ItemViewPainter.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4ItemViewPainter.h
The GTK4 backend's item-view ink: the half of this backend's three item-view seats that DRAWS a row.
It draws nothing of its own invention. Where a row goes comes from ICoreItemViewCore, and what it looks like comes from the ICoreStyleSpec the theme already emits -- the same split ICoreGtk4ScrollBarPainter and ICoreGtk4SplitterPainter made, for the same reason: a theme edit has to move these surfaces with no edit here.
⚠⚠ IT EXISTS BECAUSE THERE ARE THREE VIEWS AND NOT ONE. ICoreTree, ICoreTreeView and ICoreListBox each paint a band, a wash, an accent edge, a hairline, an expander and a column of text, and on the sibling backends those six are written out once per view -- ICoreWinUITree.cpp and ICoreWinUITreeView.cpp carry
paintRowandpaintChevronas near-identical
ICoreGtk4ItemRowInk#
ICoreGtk4ItemViewPainter.h:51 · struct · 0 declaration(s)
One row's ink, after the spec's state rules have been composed.
struct ICoreGtk4ItemRowInk {
public:
// The row's ground. ⚠ An INVALID background and an explicitly transparent
// one are different instructions and this record keeps them apart, exactly
// as the scroll bar's track does: `navigatorTree` says
// `background: transparent` at rest and means "paint nothing here", where a
// spec that never mentioned a background means "this theme has no opinion".
// Both end up drawing no pixel; only one of them may be overridden by a
// caller that wants a default.
ICoreRgba wash;
// The row's text colour. Invalid means the theme named none for this state,
// and the caller falls back -- to the cell's own foreground, then to the
// spec's Self rule.
ICoreRgba ink;
// Qt's `show-decoration-selected`: whether the wash reaches across the
// INDENT column as well as the row.
//
// ⚠⚠ IT IS READ FROM THE **Self** RULE FIRST, WHICH IS NOT WHERE THE
// OBVIOUS READING LOOKS -- it is a VIEW property in Qt and this tree's specs
// state it there. The .cpp carries the measurement and which sibling seat
// reads it from the wrong rule.
//
// ⚠ AND IT IS APPLIED IN EVERY STATE, which is not an oversight either: it
// describes the view's decoration model rather than one state's appearance,
// and a theme states it once.
bool spansRow = false;
// The "picked" accent bar down the left edge, from `borderLeft`. Invalid
// colour or a non-positive width means none.
//
// ⚠ DECLARED AT REST AND TRANSPARENT in this tree's specs, deliberately: a
// border takes space in every box model here, so an edge that only appears
// on hover shifts the row's text sideways under the pointer. That makes an
// edge with a transparent colour a REAL instruction -- reserve the space,
// draw nothing -- and it is why the width survives the colour being clear.
ICoreRgba leftEdge;
double leftEdgeWidth = 0.0;
// The hairline around the row, from `border`.
ICoreRgba hairline;
double hairlineWidth = 0.0;
// The rounded corner the wash and the hairline take. < 0 is unset and
// draws square.
double radius = -1.0;
// The row's left inset, from the Normal rule's `padding.left`. The other
// three sides are not read: the row's HEIGHT is the core's, and its right
// edge is the column's.
double paddingLeft = 0.0;
};
};
ICoreGtk4ItemHeaderInk#
ICoreGtk4ItemViewPainter.h:122 · struct · 0 declaration(s)
The column header's band and its label ink, from ICoreStylePart::Header.
struct ICoreGtk4ItemHeaderInk {
public:
ICoreRgba background;
ICoreRgba ink;
ICoreRgba underline; // borderBottom -- "no frame, one hairline"
double underlineWidth = 0.0;
double paddingLeft = 0.0;
};
};
ICoreGtk4LineEditAccess.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4LineEditAccess.h
The GtkText inside an ICoreLineEdit, as an IMPL-SIDE header.
⚠ This is not part of the public surface and must not be included from one. Same precedent and same reasoning as ICoreGtk4ItemDelegateAccess.h next door.
⚠⚠ IT EXISTS FOR ONE REASON: THE ACCESSIBLE NODE OF A FIELD IS THE GtkText AND NOT THE AREA (L7.5, and the measurement is in ICoreGtk4Accessibility.h -- gtk_text_new() reports role NONE, which removes the node entirely). A class that DERIVES from ICoreLineEdit and is not one -- ICoreSpinBox is the only one in this tree -- has to move that node's role and value, and it cannot reach it: ICoreLineEdit::impl is private, so a subclass has no more access to the child than a stranger does.
⚠ SO THE ROUTE IS THE PUBLIC HANDLE PLUS A TYPE CHECK, not a friendship. The
Declares no class of its own — see the file.
ICoreGtk4ScrollBarPainter.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4ScrollBarPainter.h
ICoreGtk4ScrollBarInk#
ICoreGtk4ScrollBarPainter.h:54 · struct · 0 declaration(s)
One bar's ink, after the spec's state rules have been resolved.
struct ICoreGtk4ScrollBarInk {
public:
// The groove behind the thumb. ⚠ USUALLY NOT PAINTED AT ALL: this tree's
// bar is an OVERLAY, and its sheet gives the bar `background: transparent`.
// An unmentioned colour and an explicitly transparent one are different
// instructions, and this is the one surface where they have the same
// outcome, because a source-over fill at alpha 0 changes no pixel. The
// field keeps them distinguishable even so.
ICoreRgba track;
ICoreRgba thumb;
ICoreRgba thumbBorder;
double thumbBorderWidth = -1.0; // < 0 is unset, per ICoreStyleEdge
// Corner radius, from the rule's borderRadius. < 0 is unset and draws
// square corners.
//
// ⚠ AT A RADIUS OF HALF THE THICKNESS OR MORE THE THUMB IS A CAPSULE, not a
// rounded rectangle -- the tree's bar is 6px wide with a radius of 3, so its
// ends are semicircles and its corner pixels carry NO ink. That is the
// shape rather than a rounding artefact, and the suite pins the empty
// corners so a backend that silently squared them off would be caught.
double radius = -1.0;
};
};
File-scope declarations#
// The GTK4 backend's scroll bar: the half of this backend's scroll-pane seat
// that draws.
//
// It draws NOTHING of its own invention. Where the thumb goes comes from
// ICoreScrollCore, and what it looks like comes from the ICoreStyleSpec
// ICoreThemeStyleSpecs::scrollBar() already emits -- the same split
enum class ICoreGtk4ScrollThumbState { Idle, Hover, Pressed };
ICoreGtk4SpinBoxPainter.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4SpinBoxPainter.h
The GTK4 backend's spin-box chevrons: the second control this backend's branded-controls row paints.
The field underneath is NOT this file's. On this backend it is a real
GtkTextinside the text-entry seat, with the field's chrome painted around it and these two chevrons painted OVER it -- which is the same arrangement Qt has, wherepaintContentdraws the chevrons on top of everything the base already put down.It decides nothing: geometry from
ICoreSpinBoxChrome, ink from the theme tokens, pixels fromICorePainter. ⚠ It names no GTK type, for the reason the three painters beside it name none -- the drawing is the half that can be proved with no display, and a suite drives it by wrapping a cairo image surface it can read the pixels back out of.
Declares no class of its own — see the file.
ICoreGtk4SplitterPainter.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4SplitterPainter.h
ICoreGtk4SplitterHandleInk#
ICoreGtk4SplitterPainter.h:58 · struct · 0 declaration(s)
The colours one bar is drawn with, after the spec's state rules have been resolved.
struct ICoreGtk4SplitterHandleInk {
public:
ICoreRgba background;
ICoreRgba border;
double borderWidth = -1.0; // < 0 is unset, per ICoreStyleEdge
};
};
File-scope declarations#
// The GTK4 backend's splitter divider: the half of this backend's splitter seat
// that draws.
//
// GTK4 *has* a splitter -- GtkPaned -- and this backend deliberately does not
// use it. The three reasons are on the seat beside this file; the one that
// reaches here is that `splitPane`'s grab bar has three states (idle, hover,
enum class ICoreGtk4SplitterHandleState { Idle, Hover, Pressed };
ICoreGtk4StandardItemAccess.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4StandardItemAccess.h
ICoreStandardItem's toolkit half on this backend, as an IMPL-SIDE header.
⚠ This is not part of the public surface and must not be included from one. It exists for the same reason UI/Backends/Qt/Widgets/ICoreStandardItemImpl.h exists on the Qt backend: TWO files need the Impl complete -- the item's own seat defines it, and the model has to adopt a released one when appendRootRow() takes ownership. A private nested class defined inside a single .cpp cannot be named from another translation unit, and that shows up as "no matching member function", which reads like a signature problem rather than a visibility one.
⚠ THE Qt Impl IS A
QStandardItem; THIS ONE IS A NODE. There the model is a real QStandardItemModel and the item IS a toolkit object, so display text, icon, editability and every caller role live in the toolkit's own role store.
ICoreStandardItem#
ICoreGtk4StandardItemAccess.h:57 · class · bases :Impl · 10 declaration(s)
class ICoreStandardItem : :Impl {
public:
explicit Impl(ICoreStandardItem& owner);
Impl(ICoreStandardItem& owner, const ICoreString& text);
~Impl();
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
// Called from ~ICoreStandardItem so this destructor does not delete a
// wrapper that is already destroying itself.
void detachOwner();
ICoreStandardItem* owner() const;
// ⚠ setData REPLACES a role whatever type it held before, which is what a
// single variant-valued role store does on the Qt backend. Two maps would
// quietly keep both and answer with whichever was consulted first, so each
// setter erases from the other -- see the .cpp.
void setRoleText(int role, const ICoreString& value);
void setRoleInt(int role, int value);
// The role as TEXT, which is what ICoreTreeView::rowData publishes. An int
// role reads back as its decimal spelling: the Qt backend gets that from
// QVariant::toString(), and the public header's own note is the contract
// this has to meet rather than a description of Qt.
ICoreString roleAsText(int role) const;
// The row this item heads, one column per cell. `cells` is columns 1..n --
// column 0 is this record itself, which is what makes a row handle and a
// column-0 item the same thing.
ICoreString label;
ICoreIcon icon;
bool hasIcon = false;
bool editable = true;
std::map<int, ICoreString> textRoles;
std::map<int, int> intRoles;
std::vector<std::shared_ptr<Impl>> children;
std::vector<std::shared_ptr<Impl>> cells;
Impl* parentNode = nullptr;
// ⚠ A NODE'S WEAK REFERENCE TO ITSELF, SET AT INSERTION, AND IT EXISTS FOR
// ONE CALLER: ICoreTreeRow::parent(). A row handle holds a weak_ptr (it must
// not keep its row alive -- a QModelIndex does not), so building a handle
// for the PARENT needs the parent's shared_ptr. Reaching it any other way
// means searching the grandparent's children, and for a top-level row it
// means reaching the model, which a bare handle cannot do.
// enable_shared_from_this is the shorter route and is unavailable: these
// records are ALSO owned by unique_ptr before insertion, so they are not
// always in a shared_ptr at all. Empty until the record is inserted, which
// is exactly when a row handle can first name it.
std::weak_ptr<Impl> selfWeak;
ICoreStandardItem* m_owner = nullptr;
};
};
ICoreGtk4TextSeat.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4TextSeat.h
What every seat that hosts a REAL GTK text control needs and the painted seats in this zone do not. Three things, and the first two are the halves the split between this tree and GTK4 actually falls on:
the CHROME -- ground, rounded border, hover fade, focus weight -- is PAINTED here out of the portable ICoreFieldChrome numbers, because the toolkit's own field look disagrees with this tree's in every theme about radius, weight and colour; the INK -- family, size and colour of the value the user typed -- is handed to the toolkit as a PangoAttrList, because the toolkit is what draws the value, the caret and the selection, and none of those three is worth imitating.
The third is the CAPTURED KEY: a key seen on its way DOWN to the control,
Declares no class of its own — see the file.
ICoreGtk4TreeModelAccess.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4TreeModelAccess.h
ICoreTreeViewModel's half on this backend, as an IMPL-SIDE header.
⚠ This is not part of the public surface and must not be included from one. It exists because TWO files need it complete: the model's own seat defines it, and ICoreTreeView has to READ the rows out of it -- on this backend the model is not a toolkit object the view can be handed, it is a plain record the view walks. Same precedent as ICoreGtk4StandardItemAccess.h beside it and UI/Backends/Qt/Widgets/ICoreStandardItemImpl.h one zone over.
⚠ NAMING IT IS LEGAL ONLY INSIDE ICoreTreeView'S OWN NESTED CLASS, and that is not a detail to discover later:
Implis private, and the public header grantsfriend class ICoreTreeView. A nested class has its enclosing class's access, so ICoreTreeView::Impl may spell these types; a namespace-scope helper or a free function may not. ICoreTree.cpp in this zone hits the same
ICoreTreeViewModel#
ICoreGtk4TreeModelAccess.h:43 · class · bases :State · 0 declaration(s)
The wrapper's own state -- see the public header.
class ICoreTreeViewModel : :State {
public:
bool alive = true;
};
};
ICoreTreeViewModel#
ICoreGtk4TreeModelAccess.h:48 · class · bases :Impl · 4 declaration(s)
class ICoreTreeViewModel : :Impl {
public:
explicit Impl(ICoreTreeViewModel& owner);
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
// ⚠ NO SELF-DELETE, AND THAT DIVERGES FROM THE Qt SEAT ON PURPOSE -- the
// same divergence every seat in this zone records. The Qt Impl self-deletes
// because a QObject parent is a SECOND owner that reaps the model; this
// record has exactly one owner, the unique_ptr on the wrapper, so
// `delete m_owner` here would delete a wrapper nobody had transferred. The
// death path is one-directional.
void detachOwner();
std::vector<std::shared_ptr<ICoreStandardItem::Impl>> rows;
std::vector<ICoreString> headers;
ICoreNativeWidget* parent = nullptr;
ICoreTreeViewModel* m_owner = nullptr;
};
};
ICoreGtk4Widget.h#
ICoreEssentials/UI/Backends/Gtk4/Widgets/ICoreGtk4Widget.h
ICoreGtk4PointerInput#
ICoreGtk4Widget.h:60 · struct · 0 declaration(s)
The toolkit half of ICoreWidget on GTK4: it owns one GtkWidget subclass instance, it decides nothing, and it is the only file in the zone that declares one.
struct ICoreGtk4PointerInput {
public:
double x = 0.0; // widget-local
double y = 0.0;
double rootX = 0.0; // toplevel space -- this backend's "screen"
double rootY = 0.0;
unsigned int button = 0; // GDK button number; 0 for a move
unsigned int state = 0; // GdkModifierType, buttons and modifiers both
int pressCount = 0; // GtkGestureClick's n_press; 2 is a double click
// ⚠ TRUE ONLY FOR THE INNERMOST WIDGET THIS EVENT REACHES. A GTK
// controller in the bubble phase runs on every ancestor as well, which for
// a press is what Qt does too -- an ignored press really does reach the
// parent. What must NOT repeat is the application-wide work: the
// outside-press walk and the application pointer-move roster. This flag is
// what gates them.
bool firstDelivery = false;
};
};
ICoreGtk4KeyInput#
ICoreGtk4Widget.h:78 · struct · 0 declaration(s)
struct ICoreGtk4KeyInput {
public:
unsigned int keyval = 0; // GDK_KEY_*
unsigned int state = 0; // GdkModifierType
};
};
ICoreGtk4ScrollInput#
ICoreGtk4Widget.h:83 · struct · 0 declaration(s)
struct ICoreGtk4ScrollInput {
public:
double x = 0.0; // widget-local, from the last motion
double y = 0.0;
double deltaX = 0.0; // GDK's own units and GDK's own sign
double deltaY = 0.0;
int unit = 0; // GdkScrollUnit: 0 wheel, 1 surface pixels
unsigned int state = 0;
};
};
ICoreGtk4ShadowSpec#
ICoreGtk4Widget.h:99 · struct · 0 declaration(s)
One drop shadow, in plain numbers.
struct ICoreGtk4ShadowSpec {
public:
bool enabled = false;
double blur = 0.0;
double dx = 0.0;
double dy = 0.0;
double cornerRadius = 0.0;
double red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0;
};
};
ICoreGtk4Widget#
ICoreGtk4Widget.h:108 · class · pImpl · 47 declaration(s)
class ICoreGtk4Widget {
public:
ICoreGtk4Widget();
~ICoreGtk4Widget();
ICoreGtk4Widget(const ICoreGtk4Widget&) = delete;
ICoreGtk4Widget& operator=(const ICoreGtk4Widget&) = delete;
[[nodiscard]] GtkWidget* widget() const;
// ---- the tree ----------------------------------------------------------
// Takes `child` into this widget's child list. A child already parented
// elsewhere is moved. Null is a no-op.
void addChild(GtkWidget* child);
// Removes `child` if it is ours. ⚠ Does NOT unref it: the caller's C++
// owner holds the reference, which is what makes a reparent safe.
void removeChild(GtkWidget* child);
[[nodiscard]] GtkWidget* parentWidget() const;
// ---- geometry, in the PARENT's coordinates -----------------------------
// The frame this widget asks its parent for. Stored on the widget and read
// back by whichever parent allocates it -- see note 3.
void setFrame(int x, int y, int width, int height);
void frame(int& x, int& y, int& width, int& height) const;
// What `measure` answers. 0 means "no limit" on the maxima, as
// QWIDGETSIZE_MAX does at the wrapper.
void setSizeLimits(int minimumWidth, int minimumHeight,
int maximumWidth, int maximumHeight);
// L9.29 -- a LAYOUT input read by ICoreLayoutSeatCore::contentRect()
// through ICoreGtk4LayoutAccess::containerContentsMargins(), not GTK CSS
// padding (that is L6.1's, Theme/Gtk4Binding's).
void setContentsMargins(int left, int top, int right, int bottom);
// The size this widget would like if nothing constrained it -- GTK's
// NATURAL size, which is what a layout tier (L2.1) reads.
void naturalSize(int& width, int& height) const;
void minimumSize(int& width, int& height) const;
// ---- state -------------------------------------------------------------
void setVisible(bool visible);
[[nodiscard]] bool isVisible() const;
void setEnabled(bool enabled);
void setFocusable(bool focusable);
[[nodiscard]] bool hasFocus() const;
void takeFocus();
void setCursor(GdkCursor* cursor); // null restores the inherited one
void setTooltip(const std::string& text);
void setPointerTransparent(bool transparent);
// Whether a child allocated outside this widget's own box is clipped away.
//
// ⚠⚠ OFF BY DEFAULT, AND ON GTK4 THAT IS NOT THE QWidget DEFAULT. A QWidget
// clips its children to itself always; GTK4 made it a property
// (`gtk_widget_set_overflow`) whose default is VISIBLE, so a child given a
// frame taller than its parent renders in full, straight over whatever is
// beside it. Nothing warns. A tier that positions a child at a NEGATIVE
// offset to scroll it -- which is the whole of L2.5's scroll pane -- is
// invisible as such without this: the content is all there, all the time.
void setClipsChildren(bool clip);
// ---- the two snapshot effects (L7.1) -----------------------------------
//
// ⚠⚠ THESE ARE SNAPSHOT OPERATIONS, NOT OBJECTS INSTALLED ON THE WIDGET,
// and that is the whole difference from the three sibling backends. Qt has
// `setGraphicsEffect`, AppKit a layer filter, WinUI a composition visual --
// each an OBJECT the target owns. GTK4 has neither: what it has is a render
// node tree, so a shadow is a node appended before the body and an opacity
// is a node pushed around it. The wrapper's ownership story is unchanged
// (the target owns the effect); what the target actually holds is this
// state, and the effect wrapper writes it.
//
// ⚠ THE SHADOW IS APPENDED BEFORE THE BODY AND OUTSIDE IT. GSK's outset
// shadow draws around a rounded rectangle rather than around the widget's
// real silhouette -- so a widget whose painted body is not that rectangle
// casts the rectangle's shadow. The corner radius is therefore the caller's
// to state, and `ICoreDropShadow`'s seat takes it from the widget's own
// solid-background radius where it has one.
void setShadow(const ICoreGtk4ShadowSpec& spec);
[[nodiscard]] ICoreGtk4ShadowSpec shadow() const;
// 1.0 is opaque and is the default. ⚠ IT WRAPS THE CHILDREN TOO, which is
// what `QGraphicsOpacityEffect` does and what every fade in this tree
// expects: a panel fading in fades with its contents rather than becoming a
// ghost frame around solid children.
void setSnapshotOpacity(double opacity);
[[nodiscard]] double snapshotOpacity() const;
void requestRepaint();
void requestRepaint(int x, int y, int width, int height);
void raiseAboveSiblings();
[[nodiscard]] bool isUnderPointer() const;
[[nodiscard]] double devicePixelRatio() const;
// ---- coordinates -- see note 2 above -----------------------------------
void mapToRoot(double localX, double localY, double& rootX, double& rootY) const;
void mapFromRoot(double rootX, double rootY, double& localX, double& localY) const;
// Whether `descendant` is inside this widget's subtree. False for null and
// for the widget itself, which is what QWidget::isAncestorOf means.
[[nodiscard]] bool isAncestorOf(GtkWidget* descendant) const;
// ---- rendering ---------------------------------------------------------
// Draws this widget AND its children into `cr`, in the widget's OWN
// coordinates, through the same snapshot path the compositor uses. Silent
// for a widget with no allocation.
//
// ⚠⚠ IT ANSWERS ONLY WHAT THE TOOLKIT CAN DRAW RIGHT NOW, AND ON GTK4 THAT
// IS A REAL CONSTRAINT RATHER THAN A CAVEAT. Rendering is a RETAINED node
// tree and there is no synchronous "draw this widget"; a widget whose
// LAYOUT is pending -- one that has just been given a child, resized or had
// a size request changed -- has no current allocation and renders EMPTY,
// with a GTK warning and no error anywhere the caller can see. A caller
// must let one frame run between changing the widget and rendering it. The
// .cpp carries the measurement of which invalidation breaks which route.
void renderTo(cairo_t* cr) const;
// ---- hooks -------------------------------------------------------------
// The cairo context is borrowed and is valid only for the duration of the
// call; the two ints are the widget's own size, so the paint origin is
// always (0, 0).
void setPaintHook(std::function<void(cairo_t*, int width, int height)> hook);
// The same contract as setPaintHook, run AFTER the children have been
// snapshotted rather than before. ⚠ IT IS THE ONLY WAY TO DRAW OVER A REAL
// TOOLKIT CONTROL THIS AREA HOSTS -- a GtkTextView paints its own text
// during the chain-up, so a seat wanting a wash or a band on top of it
// (ICoreRichTextEdit::paintOverlay, L5.3) cannot use the hook above.
void setOverlayPaintHook(std::function<void(cairo_t*, int width, int height)> hook);
void setResizedHook(std::function<void(int width, int height)> hook);
void setHoverHook(std::function<void(bool inside)> hook);
void setFocusHook(std::function<void(bool focused)> hook);
// ---- the input hooks (L1.4) -------------------------------------------
//
// ⚠⚠ THE PRESS AND KEY HOOKS RETURN "HANDLED", AND THE RETURN IS WIRED TO
// PROPAGATION RATHER THAN DISCARDED. GTK runs a bubble-phase controller on
// every ancestor of the pressed widget, which is exactly Qt's behaviour for
// an IGNORED press -- so answering false lets the parent see it, and
// answering true claims the gesture's sequence so nothing above does. A
// hook whose answer was thrown away would deliver one press to every widget
// in the chain.
// What this widget would LIKE to be, as distinct from the floor
// setSizeLimits() states. 0 on an axis means "no preference", and the floor
// then answers both questions -- which is what every seat that does not call
// this gets. See AreaState::naturalWidth for the family that needs the
// distinction and what it cost not to have it.
void setNaturalSize(int width, int height);
void setPointerPressedHook(std::function<bool(const ICoreGtk4PointerInput&)> hook);
void setPointerReleasedHook(std::function<bool(const ICoreGtk4PointerInput&)> hook);
void setPointerMovedHook(std::function<bool(const ICoreGtk4PointerInput&)> hook);
void setKeyPressedHook(std::function<bool(const ICoreGtk4KeyInput&)> hook);
void setKeyReleasedHook(std::function<bool(const ICoreGtk4KeyInput&)> hook);
void setScrollHook(std::function<bool(const ICoreGtk4ScrollInput&)> hook);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};