StyledText: capsule background for inline code (design discussion)
#12176
Replies: 4 comments 2 replies
-
|
Thanks for raising these question. For example, markdown is for the semantic information, it tells that something should be code, or link, or table, or title, but it doesn't tell how to represent that, what color or font or anything. We already have a I wonder if we should have something like Or we could also have a property I'm just brainstorming my thought here, i don't have a solution at the moment. |
Beta Was this translation helpful? Give feedback.
-
|
Agreed on the framing — Quick read on the three brainstormed options:
I'd like to suggest a different shape that leverages Slint's existing two-tier widget architecture — the same split as Tier 1 —
|
Beta Was this translation helpful? Give feedback.
-
An update on proof-of-concept RichText
|
Beta Was this translation helpful? Give feedback.
-
|
@tronical yet in my test implementation, but I recon it wouldn't need this kind of padding, because it's a paragraph feature, not inline. It will work the same as blockquotes (see screenshots above). Where the padding idea came fromThe GitHub / Discord / docs-site convention of rendering inline Iteration 1 plan (this PR)Reduced scope, no text modification:
Selection, hit-testing, copy/paste, and future cursor navigation all work normally because the underlying text matches what the user typed. Proposed roadmap if we ever want true paddingFor the record: there is a clean way to add padding without touching the text, but it needs a small contribution to parley first. I checked the parley crate and it is quite active. The reason our InlineBox attempt didn't work is // We can always line break after an inline box
self.state.mark_line_break_opportunity();That break-after-every-inline-box is hardcoded. If a A minimal parley change would fix this for our use case (and others — emoji inside a word, math operators that should stay glued to their operands, etc.): // parley/src/inline_box.rs
pub struct InlineBox {
pub id: u64,
pub kind: InlineBoxKind,
pub index: usize,
pub width: f32,
pub height: f32,
pub allow_break_after: bool, // default: true (backward compat)
}
// parley/src/layout/line_break.rs
if inline_box.allow_break_after {
self.state.mark_line_break_opportunity();
}~10 lines of diff, fully backwards compatible. With that landed, our padding becomes: builder.push_inline_box(InlineBox {
width: PAD, height: 0.0,
allow_break_after: false, // glue padding to code
..
});
// code styling...
builder.push_inline_box(InlineBox {
width: PAD, height: 0.0,
allow_break_after: true, // wrap may happen after code+padding
..
});Layout, hit-testing, selection, and copy/paste all stay correct because the boxes don't add to text length, but they do contribute width to the line. I can pursue this as a parley contribution separately if/when there's need for the visual upgrade (Personally, I need it) — happy to drive it. For this PR it's not needed; the "no padding" version stands on its own. PR contentsIf we decide on current implementation and arch, I can make PR with the inline code shortle and follow up with PRs for other components (after discussions of course). Will update the branch with:
QuestionsWhat do you think about separate components for |
Beta Was this translation helpful? Give feedback.






Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Working Examples
Context
StyledText's inlinecode(markdown backticks) currently renders as just amonospace font swap — visually almost indistinguishable from body text. Every
markdown viewer most users see daily (GitHub, Discord, LSP popups, IDE tooltips,
docs sites) renders inline code as a small "capsule": slightly smaller font, a
translucent rounded background, a thin border, padding so the text doesn't sit
flush against the surrounding words.
I have a working implementation that brings
StyledTextclose to that visualbaseline, but a couple of the decisions touch public API or
from_markdown'soutput, so I'd like to align with maintainers before opening the PR.
Proposed direction
Inline
coderenders with a translucent neutral-gray background, a 1pxrounded border, and
0.85×font size relative to surrounding text.Vertical geometry is derived from
parley::RunMetrics(cap-height,descent), so capsule size scales with the font and looks balanced for
typical content (lowercase + capitals + parens).
Theme-adaptive defaults: the border tone is picked from the luma of
text.color()so the outline stays visible on both light and darkbackgrounds. The fill is the same gray on both themes.
Three new properties on
StyledTextItemgive users full control:inline-code-background: brushinline-code-border-color: brushinline-code-font-scale: floatTransparent brushes / scale outside
(0, 2]mean "use the default".New
GlyphRenderer::fill_rectangle_with_color_radius_and_bordermethodwith overrides per renderer (Skia/Femtovg/Software) — same primitive will
be reused for block-quote bars and code-block backgrounds in follow-up
PRs.
The implementation is complete and tested locally across the three renderers
and on both light and dark themes.
Key design questions for maintainers
1. NBSP injection into
StyledTextParagraph.textThe capsule needs horizontal padding inside the rounded background that
survives line wrapping — when wrap puts the code run at the start of a
new line, the padding must come with it, not stay on the previous line.
I tried four approaches before settling on the fifth:
parley::LetterSpacingon surrounding spacesparley::InlineBox(InFlow, w=4, h=0)mark_line_break_opportunity()after every inline box (line_break.rs:531) — wrap separates before-box from code\u{00A0}injected into the paragraph text by the parserExample of warp issue:

With proposed fix:

The trade-off is that
StyledTextParagraph.textfromfrom_markdownnowcontains NBSP characters around each inline-code span. This leaks into:
selection_geometryranges include the NBSPs)codeyields\u{a0}code\u{a0})paragraph.textQuestion: is this trade-off acceptable, or do you see a path I missed?
Alternatives I considered and rejected:
LayoutWithoutLineBreaksBuilder::buildinstead of theparser, with a translation map between "original" and "parley" byte
positions. Doable but fragile — selection ranges, link ranges, hit-testing
all need translation.
boxes. Significant ongoing maintenance cost.
If the NBSP approach is a non-starter, the runner-up is probably
build-level injection with a translation map — happy to explore if needed.
2. New properties on
StyledTextItemProposed names and types:
Sub-questions:
seem to favor flat properties (e.g.
default-color,default-font-size,default-font-familyrather than adefault-font: { ... }). I went flatfor consistency.
brushvscolorfor the background/border? I chosebrushso auser could pass a gradient if they really want. The actual implementation
resolves it down to a single
ColorviaBrush::color()— the renderingprimitive paints a solid fill. Should this be
colorinstead to make thecapability explicit?
font-scale: floatvsfont-size: length. Scale is relative to thesurrounding text size, which feels right for inline code. Absolute size
would also be reasonable for users who want a fixed code size regardless
of body text. I picked scale; happy to switch or expose both.
useful is probably
inline-code-background+inline-code-font-scale.Border could fall back to a derivation of background. I have all three
implemented, but happy to trim if maintainers prefer staged API growth.
3. Theme-adaptive default border
When
inline-code-border-coloris not set, the implementation picks one oftwo grays based on
0.299·R + 0.587·G + 0.114·Boftext.color():text.color()luma > 140 → bright gray (assumed dark theme background)The fill is always the same neutral gray on both themes (overlays well on
either background) — only the border switches.
This is a heuristic. It works for typical light/dark themes but can miss on
unusual palettes (mid-gray text, high-contrast themes). The user can always
override explicitly via the property.
Question: is this kind of implicit theme detection acceptable as a
fallback when the property is unset, or do you prefer to never read
text.color()for this purpose and instead require explicit configuration?4. Follow-up PRs in the same area
This PR also establishes shared infrastructure that I'd like to reuse for
subsequent markdown features. Specifically:
GlyphRenderer::fill_rectangle_with_color_radius_and_border— will bereused for block-quote accent bars, code-block backgrounds, horizontal
rules.
default_text_color: ColorthroughLayout::drawandTextParagraph::draw— same plumbing will carry future theme-adaptiveparagraph decorations.
TextParagraph.code_ranges: Vec<Range<usize>>— pattern for "thisparagraph has rendered ranges I need to find again at draw time" that
could generalize to highlighted text, search results, etc.
Roadmap for the broader series (in case it changes how you'd review this
PR): headings (H1–H6), horizontal rules, blockquotes, code blocks, sub/sup,
HTML aliases,
<small>, images. Each as its own small PR.What I'm asking
Mainly directional sign-off on questions 1 and 2 — the parts that touch
public observable behavior. If the NBSP approach is OK and the property
shape is roughly right, I'll open the PR with the full implementation,
tests, and changelog. If either needs to change, I'd rather adjust now than
after a 1500-line review.
Happy to share the local test app or visual screenshots in either theme
on request.
Beta Was this translation helpful? Give feedback.
All reactions