Fix the horizontal scrolling for nested blocks.

This commit is contained in:
John Preston
2026-06-03 14:29:03 +04:00
parent b68b43e07f
commit 4ddd36665b
8 changed files with 677 additions and 48 deletions

View File

@@ -168,6 +168,44 @@ void EnableQTextEditLineMetrics(style::Markdown &style) {
return false;
}
struct InlineFieldTrimResult {
TextWithTags text;
int left = 0;
};
[[nodiscard]] InlineFieldTrimResult TrimInlineFieldText(
TextWithTags text,
bool trimLeft) {
auto from = 0;
auto till = text.text.size();
if (trimLeft) {
while (from < till && text.text[from].isSpace()) {
++from;
}
}
while (till > from && text.text[till - 1].isSpace()) {
--till;
}
if (from == 0 && till == text.text.size()) {
return { std::move(text), 0 };
}
text.text = text.text.mid(from, till - from);
for (auto i = text.tags.begin(); i != text.tags.end();) {
const auto tagFrom = i->offset;
const auto tagTill = i->offset + i->length;
const auto clippedFrom = std::max(tagFrom, from);
const auto clippedTill = std::min(tagTill, till);
if (clippedTill <= clippedFrom || i->length <= 0) {
i = text.tags.erase(i);
} else {
i->offset = clippedFrom - from;
i->length = clippedTill - clippedFrom;
++i;
}
}
return { std::move(text), from };
}
[[nodiscard]] QString ValidateInstantViewEditorLink(QString link) {
const auto normal = qthelp::validate_url(link);
if (!normal.isEmpty()) {
@@ -2177,16 +2215,24 @@ void Widget::setInlineFieldFromActiveState(int selectionFrom, int selectionTo) {
_settingField = true;
auto cursorSelectionFrom = selectionFrom;
auto cursorSelectionTo = selectionTo;
auto trimmedLeft = 0;
const auto trimLeft = !_state->codeBlockLanguage(
_state->activeTextOrdinal()).has_value();
if (_state->activeFieldMode() == State::FieldMode::Raw) {
_field->setTextWithTags(
const auto trimmed = TrimInlineFieldText(
{ _state->activeRawText(), {} },
trimLeft);
_field->setTextWithTags(
trimmed.text,
Ui::InputField::HistoryAction::Clear);
trimmedLeft = trimmed.left;
_article->clearEditableHeightOverride();
} else {
const auto activeText = ConvertRichTextToEditorTags(
_state->activeText());
const auto trimmed = TrimInlineFieldText(activeText.text, trimLeft);
_field->setTextWithTags(
activeText.text,
trimmed.text,
Ui::InputField::HistoryAction::Clear);
cursorSelectionFrom = MapRichTextOffsetToEditorOffset(
activeText.replacements,
@@ -2194,7 +2240,10 @@ void Widget::setInlineFieldFromActiveState(int selectionFrom, int selectionTo) {
cursorSelectionTo = MapRichTextOffsetToEditorOffset(
activeText.replacements,
selectionTo);
trimmedLeft = trimmed.left;
}
cursorSelectionFrom -= trimmedLeft;
cursorSelectionTo -= trimmedLeft;
auto cursor = _field->textCursor();
const auto size = int(_field->getLastText().size());
const auto from = std::clamp(cursorSelectionFrom, 0, size);
@@ -2260,6 +2309,32 @@ void Widget::activateTextOrdinal(
_field->setFocusFast();
}
QRect Widget::activeInlineFieldRevealRect() const {
const auto raw = _field->rawTextEdit();
const auto cursor = _field->textCursor();
auto positionCursor = cursor;
positionCursor.setPosition(cursor.position());
auto revealRect = raw->cursorRect(positionCursor);
if (cursor.hasSelection()) {
auto anchorCursor = cursor;
anchorCursor.setPosition(cursor.anchor());
revealRect = revealRect.united(raw->cursorRect(anchorCursor));
}
if (!revealRect.isValid() || revealRect.isEmpty()) {
return _field->rect();
}
revealRect.moveTopLeft(
raw->viewport()->mapTo(_field, revealRect.topLeft()));
return revealRect;
}
QRect Widget::mapFieldLocalRectToScrollContent(
QWidget *inner,
QRect rect) const {
rect.moveTopLeft(_field->mapTo(inner, rect.topLeft()));
return rect;
}
void Widget::revealActiveInlineField() {
if (_field->isHidden() || _activeSegmentIndex < 0) {
return;
@@ -2272,10 +2347,9 @@ void Widget::revealActiveInlineField() {
}
const auto scrollIn = [&](auto &&scroll) {
if (const auto inner = scroll->widget()) {
const auto fieldRect = _field->geometry();
const auto localRect = QRect(
inner->mapFromGlobal(mapToGlobal(fieldRect.topLeft())),
fieldRect.size());
const auto localRect = mapFieldLocalRectToScrollContent(
inner,
activeInlineFieldRevealRect());
scroll->scrollToY(
localRect.y(),
localRect.y() + localRect.height());

View File

@@ -211,6 +211,10 @@ private:
void ensureArticleLayoutForInlineField(int width);
void syncArticleVisibleTopBottom();
void syncInlineFieldGeometry(int width);
[[nodiscard]] QRect activeInlineFieldRevealRect() const;
[[nodiscard]] QRect mapFieldLocalRectToScrollContent(
QWidget *inner,
QRect rect) const;
void revealActiveInlineField();
void clearSelection();
void clearTextSelection();

View File

@@ -370,6 +370,7 @@ Markdown {
blockSkips: MarkdownBlockSkips;
list: MarkdownList;
quoteIndent: pixels;
quoteReadableMinWidth: pixels;
quotePaintColors: MarkdownQuotePaintColors;
pullquote: MarkdownPullquote;
rule: MarkdownRule;
@@ -730,6 +731,7 @@ defaultMarkdown: Markdown {
blockSkips: defaultMarkdownBlockSkips;
list: defaultMarkdownList;
quoteIndent: 18px;
quoteReadableMinWidth: 160px;
quotePaintColors: defaultMarkdownQuotePaintColors;
pullquote: defaultMarkdownPullquote;
rule: defaultMarkdownRule;
@@ -983,6 +985,7 @@ messageMarkdown: Markdown(defaultMarkdown) {
}
}
quoteIndent: 16px;
quoteReadableMinWidth: 160px;
quotePaintColors: messageMarkdownQuotePaintColors;
pullquote: messageMarkdownPullquote;
rule: messageMarkdownRule;

View File

@@ -1007,6 +1007,8 @@ void RestoreLogicalBlockGeometry(LaidOutBlock *block) {
block->tableScrollbarThumbRect = QRect();
block->scrollScrollbarThumbRect = QRect();
block->visibleMediaRect = block->mediaRect;
block->insideHorizontalScroll = false;
block->horizontalScrollAncestorShift = 0;
for (auto &row : block->tableRows) {
row.outer = row.logicalOuter;
for (auto &cell : row.cells) {
@@ -1075,6 +1077,8 @@ void RestoreLogicalBlockGeometry(LaidOutBlock *block) {
void ApplyTranslatedDescendantGeometry(
LaidOutBlock *block,
const ActiveHorizontalScrollOwnerState &state) {
block->insideHorizontalScroll = true;
block->horizontalScrollAncestorShift = state.shift;
block->outer = ClipRectToViewport(
TranslateRect(block->outer, state.shift),
state.viewport);

View File

@@ -757,6 +757,42 @@ int FlowBlockMinimumWidth(
return ReadableTextMinWidth(TextStyleFor(prepared, st));
}
int FlowBlockPreferredWidth(
const PreparedBlock &prepared,
const std::vector<PreparedFormulaSlot> &formulas,
const style::Markdown &st) {
if (IsAnchorOnlyBlock(prepared)) {
return 1;
}
const auto &textStyle = TextStyleFor(prepared, st);
const auto mediaRuntime = std::shared_ptr<MediaRuntime>();
const auto inlineFormulaObjects = CreateInlineFormulaObjectCache(nullptr);
auto leaf = Ui::Text::String();
SetTextLeaf(
&leaf,
textStyle,
st,
prepared.text,
&formulas,
inlineFormulaObjects.get(),
mediaRuntime,
1);
const auto usePlaceholder = prepared.text.text.isEmpty()
&& !prepared.editPlaceholderText.isEmpty();
if (!usePlaceholder) {
return leaf.maxWidth();
}
auto placeholderText = QString();
auto placeholderLeaf = Ui::Text::String();
SetEditPlaceholderLeaf(
&placeholderText,
&placeholderLeaf,
prepared.editPlaceholderText,
textStyle,
1);
return placeholderLeaf.maxWidth();
}
int CodeBlockMinimumWidth(const style::Markdown &st) {
const auto padding = BlockquotePadding(st.code.pre);
const auto content = std::max({
@@ -767,6 +803,37 @@ int CodeBlockMinimumWidth(const style::Markdown &st) {
return HorizontalMarginsWidth(padding) + content;
}
int CodeBlockPreferredWidth(
const PreparedBlock &prepared,
const style::Markdown &st) {
const auto padding = BlockquotePadding(st.code.pre);
const auto usePlaceholder = prepared.text.text.isEmpty()
&& !prepared.editPlaceholderText.isEmpty();
if (usePlaceholder) {
auto placeholderText = QString();
auto placeholderLeaf = Ui::Text::String();
SetEditPlaceholderLeaf(
&placeholderText,
&placeholderLeaf,
prepared.editPlaceholderText,
st.code,
1);
return HorizontalMarginsWidth(padding) + placeholderLeaf.maxWidth();
}
const auto mediaRuntime = std::shared_ptr<MediaRuntime>();
auto leaf = Ui::Text::String();
SetTextLeaf(
&leaf,
st.code,
st,
CodeBlockDisplayText(prepared.text),
nullptr,
nullptr,
mediaRuntime,
1);
return HorizontalMarginsWidth(padding) + leaf.maxWidth();
}
int DisplayMathMinimumWidth(
const PreparedBlock &prepared,
const std::vector<PreparedFormulaSlot> &formulas,
@@ -797,6 +864,47 @@ int DisplayMathMinimumWidth(
});
}
int DisplayMathPreferredWidth(
const PreparedBlock &prepared,
const std::vector<PreparedFormulaSlot> &formulas,
const style::Markdown &st) {
const auto &padding = st.displayMath.padding;
if (const auto formula = PreparedFormulaFor(formulas, prepared.formulaIndex);
formula && formula->measured.success) {
return HorizontalMarginsWidth(padding)
+ std::max(formula->measured.logicalSize.width(), 1);
}
const auto &fallbackPadding = st.displayMath.fallbackPadding;
const auto usePlaceholder = prepared.formulaTex.trimmed().isEmpty()
&& !prepared.editPlaceholderText.isEmpty();
if (usePlaceholder) {
auto placeholderText = QString();
auto placeholderLeaf = Ui::Text::String();
SetEditPlaceholderLeaf(
&placeholderText,
&placeholderLeaf,
prepared.editPlaceholderText,
st.displayMath.fallbackStyle,
1);
return HorizontalMarginsWidth(padding)
+ HorizontalMarginsWidth(fallbackPadding)
+ placeholderLeaf.maxWidth();
}
auto fallbackText = TextWithEntities::Simple(u"Invalid formula"_q);
fallbackText.entities.push_back(EntityInText(
EntityType::Italic,
0,
fallbackText.text.size()));
auto leaf = Ui::Text::String(TextMinResizeWidth(1));
leaf.setMarkedText(
st.displayMath.fallbackStyle,
std::move(fallbackText),
kIvMarkedTextOptions);
return HorizontalMarginsWidth(padding)
+ HorizontalMarginsWidth(fallbackPadding)
+ leaf.maxWidth();
}
int ContainerMinimumWidth(
int contentMinimumWidth,
QMargins padding) {

View File

@@ -156,8 +156,10 @@ struct LaidOutBlock {
bool tableStriped = false;
bool supplementary = false;
bool pullquote = false;
bool insideHorizontalScroll = false;
int horizontalScrollLeft = 0;
int horizontalScrollMax = 0;
int horizontalScrollAncestorShift = 0;
int segmentIndex = -1;
int secondarySegmentIndex = -1;
int tertiarySegmentIndex = -1;
@@ -254,11 +256,22 @@ struct TableRowLayoutData {
[[nodiscard]] int FlowBlockMinimumWidth(
const PreparedBlock &prepared,
const style::Markdown &st);
[[nodiscard]] int FlowBlockPreferredWidth(
const PreparedBlock &prepared,
const std::vector<PreparedFormulaSlot> &formulas,
const style::Markdown &st);
[[nodiscard]] int CodeBlockMinimumWidth(const style::Markdown &st);
[[nodiscard]] int CodeBlockPreferredWidth(
const PreparedBlock &prepared,
const style::Markdown &st);
[[nodiscard]] int DisplayMathMinimumWidth(
const PreparedBlock &prepared,
const std::vector<PreparedFormulaSlot> &formulas,
const style::Markdown &st);
[[nodiscard]] int DisplayMathPreferredWidth(
const PreparedBlock &prepared,
const std::vector<PreparedFormulaSlot> &formulas,
const style::Markdown &st);
[[nodiscard]] int ContainerMinimumWidth(
int contentMinimumWidth,
QMargins padding);

View File

@@ -225,10 +225,17 @@ void RefreshLogicalGeometry(LaidOutBlock *block) {
struct WidthAnalysisNode {
std::vector<WidthAnalysisNode> children;
int contentMinimumWidth = 1;
int contentPreferredWidth = 1;
int outerMinimumWidth = 1;
int outerPreferredWidth = 1;
int scrollOwnerMinimumWidth = 1;
int scrollOwnerOverflowWidth = 1;
int scrollViewportMinimumWidth = 1;
int outerScrollViewportMinimumWidth = 1;
bool ownerEligible = false;
bool chosenScrollOwner = false;
bool subtreeNeedsScrollOwner = false;
bool subtreeHasChildMovingScrollOwner = false;
bool subtreeHasScrollOwner = false;
};
@@ -241,17 +248,100 @@ struct WidthAnalysisNode {
return result;
}
[[nodiscard]] int MaxChildOuterPreferredWidth(
const std::vector<WidthAnalysisNode> &children) {
auto result = 1;
for (const auto &child : children) {
result = std::max(result, child.outerPreferredWidth);
}
return result;
}
[[nodiscard]] bool HasUnresolvedChildScrollOwner(
const std::vector<WidthAnalysisNode> &children) {
for (const auto &child : children) {
if (child.subtreeNeedsScrollOwner) {
return true;
}
}
return false;
}
void FinalizeOwnerSelection(
WidthAnalysisNode *analysis,
int availableWidth) {
analysis->subtreeHasScrollOwner = false;
int availableWidth,
int visibleViewportWidth,
bool ownerMovesChildren) {
auto childNeedsScrollOwner = false;
auto childHasChildMovingScrollOwner = false;
auto childHasScrollOwner = false;
for (const auto &child : analysis->children) {
analysis->subtreeHasScrollOwner |= child.subtreeHasScrollOwner;
childNeedsScrollOwner |= child.subtreeNeedsScrollOwner;
childHasChildMovingScrollOwner
|= child.subtreeHasChildMovingScrollOwner;
childHasScrollOwner |= child.subtreeHasScrollOwner;
}
analysis->chosenScrollOwner = !analysis->subtreeHasScrollOwner
const auto nodeOverflows = analysis->scrollOwnerOverflowWidth
> std::max(availableWidth, 1);
const auto subtreeNeedsScrollOwner = childNeedsScrollOwner
|| (!childHasChildMovingScrollOwner && nodeOverflows);
const auto viewportIsReadable = std::max(visibleViewportWidth, 1)
>= std::max(analysis->scrollViewportMinimumWidth, 1);
analysis->chosenScrollOwner = subtreeNeedsScrollOwner
&& analysis->ownerEligible
&& (analysis->scrollOwnerMinimumWidth > std::max(availableWidth, 1));
analysis->subtreeHasScrollOwner |= analysis->chosenScrollOwner;
&& viewportIsReadable;
analysis->subtreeNeedsScrollOwner = subtreeNeedsScrollOwner
&& !analysis->chosenScrollOwner;
analysis->subtreeHasChildMovingScrollOwner
= childHasChildMovingScrollOwner
|| (analysis->chosenScrollOwner && ownerMovesChildren);
analysis->subtreeHasScrollOwner = childHasScrollOwner
|| analysis->chosenScrollOwner;
}
[[nodiscard]] bool AnalysisOwnerMovesChildren(PreparedBlockKind kind) {
switch (kind) {
case PreparedBlockKind::List:
case PreparedBlockKind::ListItem:
case PreparedBlockKind::Quote:
case PreparedBlockKind::EmbedPost:
case PreparedBlockKind::Details:
return true;
case PreparedBlockKind::Paragraph:
case PreparedBlockKind::Thinking:
case PreparedBlockKind::Heading:
case PreparedBlockKind::CodeBlock:
case PreparedBlockKind::Rule:
case PreparedBlockKind::DisplayMath:
case PreparedBlockKind::Table:
case PreparedBlockKind::Photo:
case PreparedBlockKind::Video:
case PreparedBlockKind::Audio:
case PreparedBlockKind::Map:
case PreparedBlockKind::Channel:
case PreparedBlockKind::GroupedMedia:
case PreparedBlockKind::RelatedArticle:
case PreparedBlockKind::Placeholder:
return false;
}
return false;
}
[[nodiscard]] int ReadableScrollViewportMinimumWidth(
int contentMaxWidth,
const style::Markdown &st) {
return std::min(
std::max(contentMaxWidth, 1),
std::max(st.quoteReadableMinWidth, 1));
}
[[nodiscard]] int QuoteRelatedScrollViewportMinimumWidth(
LayoutContext context,
int contentMaxWidth,
const style::Markdown &st) {
return (context.quoteDepth > 0)
? ReadableScrollViewportMinimumWidth(contentMaxWidth, st)
: 1;
}
[[nodiscard]] int OrderedMarkerMinimumWidth(
@@ -337,6 +427,15 @@ void FinalizeOwnerSelection(
: result;
}
[[nodiscard]] int ChildLayoutWidth(
const WidthAnalysisNode *activeScrollOwner,
int visibleWidth,
int logicalWidth) {
return activeScrollOwner
? std::max(logicalWidth, 1)
: std::max(visibleWidth, 1);
}
[[nodiscard]] int ScrollbarReserveHeight(
bool scrollOwner,
int horizontalScrollMax,
@@ -390,34 +489,135 @@ void FinalizeOwnerSelection(
LayoutContext context) {
auto analysis = WidthAnalysisNode();
const auto availableWidth = std::max(width, 1);
auto visibleScrollViewportWidth = availableWidth;
switch (prepared.kind) {
case PreparedBlockKind::Paragraph:
case PreparedBlockKind::Thinking:
case PreparedBlockKind::Heading:
analysis.contentMinimumWidth = FlowBlockMinimumWidth(prepared, st);
analysis.outerMinimumWidth = analysis.contentMinimumWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.ownerEligible = !IsAnchorOnlyBlock(prepared);
break;
case PreparedBlockKind::CodeBlock:
analysis.contentMinimumWidth = CodeBlockMinimumWidth(st);
analysis.outerMinimumWidth = analysis.contentMinimumWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.ownerEligible = true;
break;
case PreparedBlockKind::DisplayMath:
analysis.contentMinimumWidth = DisplayMathMinimumWidth(
analysis.contentPreferredWidth = FlowBlockPreferredWidth(
prepared,
formulas,
st);
analysis.outerMinimumWidth = analysis.contentMinimumWidth;
analysis.outerPreferredWidth = analysis.contentPreferredWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.ownerEligible = true;
analysis.scrollViewportMinimumWidth
= QuoteRelatedScrollViewportMinimumWidth(
context,
analysis.contentPreferredWidth,
st);
analysis.outerScrollViewportMinimumWidth
= analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(
analysis.outerMinimumWidth,
analysis.outerPreferredWidth)
: analysis.outerMinimumWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerMinimumWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.ownerEligible = !IsAnchorOnlyBlock(prepared);
break;
case PreparedBlockKind::CodeBlock: {
const auto padding = BlockquotePadding(st.code.pre);
visibleScrollViewportWidth = availableWidth
- HorizontalMarginsWidth(padding);
analysis.contentMinimumWidth = CodeBlockMinimumWidth(st);
analysis.contentPreferredWidth = CodeBlockPreferredWidth(prepared, st);
analysis.outerMinimumWidth = analysis.contentMinimumWidth;
analysis.outerPreferredWidth = analysis.contentPreferredWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
const auto contentMaxWidth = std::max(
analysis.contentPreferredWidth - HorizontalMarginsWidth(padding),
1);
analysis.scrollViewportMinimumWidth
= QuoteRelatedScrollViewportMinimumWidth(
context,
contentMaxWidth,
st);
analysis.outerScrollViewportMinimumWidth
= HorizontalMarginsWidth(padding)
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(
analysis.outerMinimumWidth,
analysis.outerPreferredWidth)
: analysis.outerMinimumWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerMinimumWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.ownerEligible = true;
} break;
case PreparedBlockKind::DisplayMath: {
const auto &padding = st.displayMath.padding;
visibleScrollViewportWidth = availableWidth
- HorizontalMarginsWidth(padding);
analysis.contentMinimumWidth = DisplayMathMinimumWidth(
prepared,
formulas,
st);
analysis.contentPreferredWidth = DisplayMathPreferredWidth(
prepared,
formulas,
st);
analysis.outerMinimumWidth = analysis.contentMinimumWidth;
analysis.outerPreferredWidth = analysis.contentPreferredWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
const auto contentMaxWidth = std::max(
analysis.contentPreferredWidth - HorizontalMarginsWidth(padding),
1);
analysis.scrollViewportMinimumWidth
= QuoteRelatedScrollViewportMinimumWidth(
context,
contentMaxWidth,
st);
analysis.outerScrollViewportMinimumWidth
= HorizontalMarginsWidth(padding)
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(
analysis.outerMinimumWidth,
analysis.outerPreferredWidth)
: analysis.outerMinimumWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerMinimumWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.ownerEligible = true;
} break;
case PreparedBlockKind::Table:
analysis.contentMinimumWidth = TableBlockMinimumWidth(prepared, st);
analysis.contentPreferredWidth = analysis.contentMinimumWidth;
analysis.outerMinimumWidth = analysis.contentMinimumWidth;
analysis.outerPreferredWidth = analysis.contentPreferredWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.scrollViewportMinimumWidth
= QuoteRelatedScrollViewportMinimumWidth(
context,
analysis.contentPreferredWidth,
st);
analysis.outerScrollViewportMinimumWidth
= analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(
analysis.outerMinimumWidth,
analysis.outerPreferredWidth)
: analysis.outerMinimumWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerMinimumWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.ownerEligible = true;
break;
case PreparedBlockKind::List: {
@@ -426,6 +626,7 @@ void FinalizeOwnerSelection(
0);
const auto overhead = depthDelta * st.list.indent;
const auto childWidth = std::max(availableWidth - overhead, 1);
visibleScrollViewportWidth = childWidth;
auto childContext = context;
childContext.listDepth = prepared.visualDepth;
childContext.tightList = false;
@@ -436,9 +637,38 @@ void FinalizeOwnerSelection(
st,
childWidth,
childContext);
const auto childNeedsScrollOwner
= HasUnresolvedChildScrollOwner(analysis.children);
analysis.contentMinimumWidth = MaxChildOuterMinimumWidth(analysis.children);
analysis.contentPreferredWidth = MaxChildOuterPreferredWidth(
analysis.children);
analysis.outerMinimumWidth = overhead + analysis.contentMinimumWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.outerPreferredWidth = overhead + analysis.contentPreferredWidth;
const auto naturalOverflowWidth = overhead
+ analysis.contentMinimumWidth;
const auto preferredOverflowWidth = overhead
+ analysis.contentPreferredWidth;
analysis.scrollViewportMinimumWidth = std::max(
QuoteRelatedScrollViewportMinimumWidth(
context,
analysis.contentPreferredWidth,
st),
childNeedsScrollOwner
? ReadableScrollViewportMinimumWidth(
analysis.contentPreferredWidth,
st)
: 1);
analysis.outerScrollViewportMinimumWidth = overhead
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(naturalOverflowWidth, preferredOverflowWidth)
: naturalOverflowWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerOverflowWidth,
preferredOverflowWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.ownerEligible = !prepared.children.empty();
} break;
case PreparedBlockKind::ListItem: {
@@ -447,6 +677,7 @@ void FinalizeOwnerSelection(
ListItemMarkerMinimumWidth(prepared, st));
const auto overhead = markerWidth + st.list.markerSkip;
const auto childWidth = std::max(availableWidth - overhead, 1);
visibleScrollViewportWidth = childWidth;
auto childContext = context;
childContext.tightList = false;
PrepareNestedContext(&childContext, 0, childWidth);
@@ -456,9 +687,38 @@ void FinalizeOwnerSelection(
st,
childWidth,
childContext);
const auto childNeedsScrollOwner
= HasUnresolvedChildScrollOwner(analysis.children);
analysis.contentMinimumWidth = MaxChildOuterMinimumWidth(analysis.children);
analysis.contentPreferredWidth = MaxChildOuterPreferredWidth(
analysis.children);
analysis.outerMinimumWidth = overhead + analysis.contentMinimumWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.outerPreferredWidth = overhead + analysis.contentPreferredWidth;
const auto naturalOverflowWidth = overhead
+ analysis.contentMinimumWidth;
const auto preferredOverflowWidth = overhead
+ analysis.contentPreferredWidth;
analysis.scrollViewportMinimumWidth = std::max(
QuoteRelatedScrollViewportMinimumWidth(
context,
analysis.contentPreferredWidth,
st),
childNeedsScrollOwner
? ReadableScrollViewportMinimumWidth(
analysis.contentPreferredWidth,
st)
: 1);
analysis.outerScrollViewportMinimumWidth = overhead
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(naturalOverflowWidth, preferredOverflowWidth)
: naturalOverflowWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerOverflowWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.ownerEligible = !prepared.children.empty();
} break;
case PreparedBlockKind::Quote: {
@@ -469,12 +729,14 @@ void FinalizeOwnerSelection(
const auto padding = prepared.pullquote
? st.pullquote.padding
: BlockquotePadding(st.body.blockquote);
const auto paddingWidth = HorizontalMarginsWidth(padding);
auto childWidth = std::max(
availableWidth - overhead - HorizontalMarginsWidth(padding),
availableWidth - overhead - paddingWidth,
1);
if (prepared.pullquote) {
childWidth = std::min(childWidth, std::max(st.pullquote.maxWidth, 1));
}
visibleScrollViewportWidth = childWidth;
auto childContext = context;
childContext.quoteDepth = prepared.visualDepth;
childContext.tightList = false;
@@ -485,16 +747,66 @@ void FinalizeOwnerSelection(
st,
childWidth,
childContext);
const auto childNeedsScrollOwner
= HasUnresolvedChildScrollOwner(analysis.children);
analysis.contentMinimumWidth = MaxChildOuterMinimumWidth(analysis.children);
analysis.contentPreferredWidth = MaxChildOuterPreferredWidth(
analysis.children);
const auto visibleContentWidth = prepared.pullquote
? std::min(
analysis.contentMinimumWidth,
std::max(st.pullquote.maxWidth, 1))
: analysis.contentMinimumWidth;
const auto visiblePreferredContentWidth = prepared.pullquote
? std::min(
analysis.contentPreferredWidth,
std::max(st.pullquote.maxWidth, 1))
: analysis.contentPreferredWidth;
analysis.outerMinimumWidth = overhead
+ HorizontalMarginsWidth(padding)
+ paddingWidth
+ visibleContentWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.outerPreferredWidth = overhead
+ paddingWidth
+ visiblePreferredContentWidth;
if (prepared.pullquote) {
analysis.scrollOwnerOverflowWidth = analysis.outerMinimumWidth;
analysis.scrollViewportMinimumWidth = childNeedsScrollOwner
? ReadableScrollViewportMinimumWidth(
analysis.contentPreferredWidth,
st)
: 1;
analysis.outerScrollViewportMinimumWidth = overhead
+ paddingWidth
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerMinimumWidth
= analysis.scrollOwnerOverflowWidth;
if (childNeedsScrollOwner) {
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerMinimumWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
}
} else {
const auto quoteScrollOwnerContentWidth = std::max(
analysis.contentPreferredWidth,
1);
analysis.scrollOwnerOverflowWidth = overhead
+ paddingWidth
+ quoteScrollOwnerContentWidth;
analysis.scrollViewportMinimumWidth
= ReadableScrollViewportMinimumWidth(
analysis.contentPreferredWidth,
st);
analysis.outerScrollViewportMinimumWidth = overhead
+ paddingWidth
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerOverflowWidth,
analysis.outerPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
}
analysis.ownerEligible = !prepared.children.empty();
} break;
case PreparedBlockKind::Details: {
@@ -514,11 +826,14 @@ void FinalizeOwnerSelection(
+ actionSkip
+ actionWidth
+ ReadableTextMinWidth(st.details.summaryStyle);
const auto bodyPaddingWidth = HorizontalMarginsWidth(bodyPadding);
auto bodyMinimumWidth = 1;
auto bodyPreferredWidth = 1;
if (!prepared.collapsed) {
const auto childWidth = std::max(
availableWidth - HorizontalMarginsWidth(bodyPadding),
availableWidth - bodyPaddingWidth,
1);
visibleScrollViewportWidth = childWidth;
auto childContext = context;
PrepareNestedContext(&childContext, 0, childWidth);
analysis.children = AnalyzeBlocks(
@@ -527,16 +842,45 @@ void FinalizeOwnerSelection(
st,
childWidth,
childContext);
const auto childNeedsScrollOwner
= HasUnresolvedChildScrollOwner(analysis.children);
analysis.contentMinimumWidth = MaxChildOuterMinimumWidth(
analysis.children);
analysis.contentPreferredWidth = MaxChildOuterPreferredWidth(
analysis.children);
bodyMinimumWidth = ContainerMinimumWidth(
analysis.contentMinimumWidth,
bodyPadding);
bodyPreferredWidth = std::max(analysis.contentPreferredWidth, 1)
+ bodyPaddingWidth;
analysis.scrollViewportMinimumWidth = std::max(
QuoteRelatedScrollViewportMinimumWidth(
context,
analysis.contentPreferredWidth,
st),
childNeedsScrollOwner
? ReadableScrollViewportMinimumWidth(
analysis.contentPreferredWidth,
st)
: 1);
analysis.outerScrollViewportMinimumWidth = bodyPaddingWidth
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerMinimumWidth = std::max({
bodyMinimumWidth,
bodyPreferredWidth,
analysis.outerScrollViewportMinimumWidth,
});
}
analysis.outerMinimumWidth = std::max(
headerMinimumWidth,
bodyMinimumWidth);
analysis.scrollOwnerMinimumWidth = bodyMinimumWidth;
analysis.outerPreferredWidth = std::max(
headerMinimumWidth,
bodyPreferredWidth);
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(bodyMinimumWidth, bodyPreferredWidth)
: bodyMinimumWidth;
analysis.ownerEligible = !prepared.collapsed
&& !prepared.children.empty();
} break;
@@ -551,17 +895,25 @@ void FinalizeOwnerSelection(
: 0;
const auto headerGap = avatarWidth ? style.headerGap : 0;
auto headerTextWidth = 1;
auto headerTextPreferredWidth = 1;
if (!prepared.embedPost.author.isEmpty()) {
headerTextWidth = std::max(
headerTextWidth,
ReadableTextMinWidth(style.authorStyle));
headerTextPreferredWidth = std::max(
headerTextPreferredWidth,
style.authorStyle.font->width(prepared.embedPost.author));
}
if (!prepared.embedPost.dateText.isEmpty()) {
headerTextWidth = std::max(
headerTextWidth,
ReadableTextMinWidth(style.dateStyle));
headerTextPreferredWidth = std::max(
headerTextPreferredWidth,
style.dateStyle.font->width(prepared.embedPost.dateText));
}
const auto childWidth = std::max(availableWidth - contentOverhead, 1);
visibleScrollViewportWidth = childWidth;
auto childContext = context;
PrepareNestedContext(&childContext, 0, childWidth);
analysis.children = AnalyzeBlocks(
@@ -570,12 +922,42 @@ void FinalizeOwnerSelection(
st,
childWidth,
childContext);
const auto childNeedsScrollOwner
= HasUnresolvedChildScrollOwner(analysis.children);
analysis.contentMinimumWidth = MaxChildOuterMinimumWidth(analysis.children);
analysis.scrollOwnerMinimumWidth = contentOverhead
analysis.contentPreferredWidth = MaxChildOuterPreferredWidth(
analysis.children);
const auto naturalOverflowWidth = contentOverhead
+ analysis.contentMinimumWidth;
const auto preferredOverflowWidth = contentOverhead
+ analysis.contentPreferredWidth;
analysis.scrollViewportMinimumWidth = std::max(
QuoteRelatedScrollViewportMinimumWidth(
context,
analysis.contentPreferredWidth,
st),
childNeedsScrollOwner
? ReadableScrollViewportMinimumWidth(
analysis.contentPreferredWidth,
st)
: 1);
analysis.outerScrollViewportMinimumWidth = contentOverhead
+ analysis.scrollViewportMinimumWidth;
analysis.scrollOwnerOverflowWidth
= (analysis.scrollViewportMinimumWidth > 1)
? std::max(naturalOverflowWidth, preferredOverflowWidth)
: naturalOverflowWidth;
analysis.scrollOwnerMinimumWidth = std::max({
analysis.scrollOwnerOverflowWidth,
preferredOverflowWidth,
analysis.outerScrollViewportMinimumWidth,
});
analysis.outerMinimumWidth = std::max(
contentOverhead + avatarWidth + headerGap + headerTextWidth,
analysis.scrollOwnerMinimumWidth);
analysis.scrollOwnerOverflowWidth);
analysis.outerPreferredWidth = std::max(
contentOverhead + avatarWidth + headerGap + headerTextPreferredWidth,
contentOverhead + analysis.contentPreferredWidth);
analysis.ownerEligible = !prepared.children.empty();
} break;
case PreparedBlockKind::Rule:
@@ -588,12 +970,18 @@ void FinalizeOwnerSelection(
case PreparedBlockKind::RelatedArticle:
case PreparedBlockKind::Placeholder:
analysis.contentMinimumWidth = 1;
analysis.contentPreferredWidth = analysis.contentMinimumWidth;
analysis.outerMinimumWidth = 1;
analysis.outerPreferredWidth = analysis.contentPreferredWidth;
analysis.scrollOwnerMinimumWidth = analysis.outerMinimumWidth;
analysis.ownerEligible = false;
break;
}
FinalizeOwnerSelection(&analysis, availableWidth);
FinalizeOwnerSelection(
&analysis,
availableWidth,
visibleScrollViewportWidth,
AnalysisOwnerMovesChildren(prepared.kind));
return analysis;
}
@@ -706,10 +1094,14 @@ void FinalizeOwnerSelection(
outerLogicalWidth - block.markerWidth - list.markerSkip,
1);
const auto childActiveScrollOwner = currentScrollOwner;
const auto bodyLayoutWidth = ChildLayoutWidth(
childActiveScrollOwner,
bodyWidth,
bodyLogicalWidth);
auto childContext = context;
childContext.tightList = tight;
PrepareNestedContext(&childContext, bodyLeft, bodyWidth);
PrepareNestedContext(&childContext, bodyLeft, bodyLayoutWidth);
const auto childBottom = LayoutBlocks(
prepared.children,
formulas,
@@ -723,7 +1115,7 @@ void FinalizeOwnerSelection(
st,
bodyLeft,
top,
bodyWidth,
bodyLayoutWidth,
bodyLogicalWidth,
childContext);
const auto markerBaseline = [&] {
@@ -839,11 +1231,15 @@ void FinalizeOwnerSelection(
outerLogicalWidth - depthDelta * st.list.indent,
1);
const auto childActiveScrollOwner = currentScrollOwner;
const auto listLayoutWidth = ChildLayoutWidth(
childActiveScrollOwner,
listWidth,
listLogicalWidth);
auto childContext = context;
childContext.listDepth = prepared.visualDepth;
childContext.tightList = false;
PrepareNestedContext(&childContext, listLeft, listWidth);
PrepareNestedContext(&childContext, listLeft, listLayoutWidth);
auto y = top;
auto previous = static_cast<const PreparedBlock*>(nullptr);
@@ -867,7 +1263,7 @@ void FinalizeOwnerSelection(
st,
listLeft,
y,
listWidth,
listLayoutWidth,
listLogicalWidth,
childContext,
prepared.tight)
@@ -883,7 +1279,7 @@ void FinalizeOwnerSelection(
st,
listLeft,
y,
listWidth,
listLayoutWidth,
listLogicalWidth,
childContext);
y = BlockBottom(laidOut);
@@ -990,11 +1386,15 @@ void FinalizeOwnerSelection(
const auto childActiveScrollOwner = NextActiveScrollOwner(
analysis,
activeScrollOwner);
const auto contentLayoutWidth = ChildLayoutWidth(
childActiveScrollOwner,
contentWidth,
contentLogicalWidth);
auto childContext = context;
childContext.quoteDepth = prepared.visualDepth;
childContext.tightList = false;
PrepareNestedContext(&childContext, contentLeft, contentWidth);
PrepareNestedContext(&childContext, contentLeft, contentLayoutWidth);
const auto childBottom = LayoutBlocks(
prepared.children,
formulas,
@@ -1008,7 +1408,7 @@ void FinalizeOwnerSelection(
st,
contentLeft,
contentTop,
contentWidth,
contentLayoutWidth,
contentLogicalWidth,
childContext);
const auto contentHeight = std::max(
@@ -1206,8 +1606,12 @@ void FinalizeOwnerSelection(
- bodyPadding.left()
- bodyPadding.right(),
1);
const auto childLayoutWidth = ChildLayoutWidth(
childActiveScrollOwner,
childWidth,
childLogicalWidth);
auto childContext = context;
PrepareNestedContext(&childContext, childLeft, childWidth);
PrepareNestedContext(&childContext, childLeft, childLayoutWidth);
const auto childBottom = LayoutBlocks(
prepared.children,
formulas,
@@ -1221,7 +1625,7 @@ void FinalizeOwnerSelection(
st,
childLeft,
childTop,
childWidth,
childLayoutWidth,
childLogicalWidth,
childContext);
const auto contentHeight = std::max(childBottom - childTop, 0);
@@ -1390,8 +1794,12 @@ void FinalizeOwnerSelection(
auto wrapperBottom = contentTop + headerHeight;
if (!prepared.children.empty()) {
const auto bodyTop = wrapperBottom + ((headerHeight > 0) ? style.bodySkip : 0);
const auto contentLayoutWidth = ChildLayoutWidth(
childActiveScrollOwner,
contentWidth,
contentLogicalWidth);
auto childContext = context;
PrepareNestedContext(&childContext, contentLeft, contentWidth);
PrepareNestedContext(&childContext, contentLeft, contentLayoutWidth);
const auto childBottom = LayoutBlocks(
prepared.children,
formulas,
@@ -1405,7 +1813,7 @@ void FinalizeOwnerSelection(
st,
contentLeft,
bodyTop,
contentWidth,
contentLayoutWidth,
contentLogicalWidth,
childContext);
const auto bodyHeight = std::max(childBottom - bodyTop, 0);

View File

@@ -1349,6 +1349,21 @@ void PaintTableCaption(
return paintSt.table.overflowFg->c;
}
[[nodiscard]] bool HorizontalRightEdgeHidden(const LaidOutBlock &block) {
return block.horizontalScrollMax > 0
&& (block.horizontalScrollLeft < block.horizontalScrollMax);
}
[[nodiscard]] QRect HorizontalScrollLogicalPaintRect(
const LaidOutBlock &block) {
if (block.insideHorizontalScroll) {
return block.logicalGeometry.outer.translated(
block.horizontalScrollAncestorShift,
0);
}
return block.outer;
}
void PaintHorizontalOverflowIndicators(
Painter &p,
const LaidOutBlock &block,
@@ -1366,7 +1381,7 @@ void PaintHorizontalOverflowIndicators(
return;
}
const auto left = (block.horizontalScrollLeft > 0);
const auto right = (block.horizontalScrollLeft < block.horizontalScrollMax);
const auto right = HorizontalRightEdgeHidden(block);
if (!left && !right) {
return;
}
@@ -1810,7 +1825,7 @@ void PaintQuoteBlock(
p.setClipRect(quoteClip);
Ui::Text::FillQuotePaint(
p,
block.outer,
HorizontalScrollLogicalPaintRect(block),
*context.caches.blockquote,
quoteStyle);
p.restore();