mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-07-14 23:12:49 +03:00
Grid layout fixes.
This commit is contained in:
@@ -910,9 +910,16 @@ Do not checkpoint a failing phase as complete.
|
||||
| 41 | I5: Negative `contentBoxSize` now clamped to 0 | (implicitly tested) |
|
||||
| 42 | L5: `mPacking` converted to RAII guard (exception-safe) | (implicitly tested) |
|
||||
| 43 | I4: Span decoupled from `resolvedEnd` — explicit `columnSpan`/`rowSpan` fields | (implicitly tested) |
|
||||
| 44 | PL7: Auto-placement cursor not advancing past placed items (`cursorCol = c+sw`) | `GridContainer.percentHeightWithAutoParentFallsBackToContent` |
|
||||
| 45 | PL8: Auto-placement column/row limit growing during iteration (captured `colLimit`/`rowLimit`) | `GridContainer.percentHeightWithAutoParentFallsBackToContent` |
|
||||
| 46 | L6: `height:100%` / `width:100%` on grid with auto parent → fall back to content-based size | `GridContainer.percentHeightWithAutoParentFallsBackToContent` |
|
||||
| 47 | TS8: `1fr` tracks get `baseSize=0` when container has indefinite width → size from item intrinsic widths | `GridContainer.frTracksWithPercentWidthAndHeight` |
|
||||
| 48 | L7: `width:100%`/`height:100%` resolved against small intermediate ancestor → scan all ancestors + scene node for max size | `GridContainer.frTracksWithPercentWidthAndHeight` |
|
||||
| 49 | L8: Percentage fallback ran AFTER track sizing → rows sized with 0 height. Re-run sizing loop after fallback adjusts container size. | `GridContainer.frTracksWithPercentWidthAndHeight` |
|
||||
| 50 | L9: `height:100%` resolved against viewport instead of auto → use content-based height (CSS spec: % height on auto parent = auto) | `GridContainer.newsblurReducedGrid` |
|
||||
|
||||
### Stats
|
||||
|
||||
- **Total bugs found**: 43
|
||||
- **Fixed**: 43
|
||||
- **Total bugs found**: 50
|
||||
- **Fixed**: 50
|
||||
- **By severity**: 0 remaining
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
"working_dir": "${project_root}/bin"
|
||||
},
|
||||
{
|
||||
"args": "-c system --hn-dark",
|
||||
"args": "-d1 /home/downloads/files/svn/eepp/bin/unit_tests/assets/html/grid_newsblur_reduced.html",
|
||||
"command": "${project_root}/bin/eepp-ui-html-debug",
|
||||
"name": "eepp-ui-html-debug",
|
||||
"working_dir": "${project_root}/bin"
|
||||
|
||||
451
bin/unit_tests/assets/html/grid_newsblur_reduced.html
Normal file
451
bin/unit_tests/assets/html/grid_newsblur_reduced.html
Normal file
@@ -0,0 +1,451 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
height: 100%;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-color: #c2c2c2 #fafafa;
|
||||
background-color: #fff;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
a,
|
||||
a:active,
|
||||
a:hover,
|
||||
a:visited,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
.NB-splash {
|
||||
display: block;
|
||||
}
|
||||
body,
|
||||
.NB-theme-feed-font-whitney {
|
||||
font-family:
|
||||
Whitney SSm A,
|
||||
Whitney SSm B,
|
||||
Lucida Grande,
|
||||
Verdana,
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
sans-serif;
|
||||
}
|
||||
.NB-welcome .NB-body-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
}
|
||||
.NB-welcome .NB-inner {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
.NB-welcome-features-wrap {
|
||||
background: linear-gradient(#f5eedd 0%, #ebe0c2 100%);
|
||||
border-top: 1px solid #0000000f;
|
||||
border-bottom: 1px solid #0000000f;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.NB-welcome-feature-groups {
|
||||
margin: 48px 0 24px;
|
||||
}
|
||||
.NB-welcome-feature-groups > .NB-inner {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 36px 48px;
|
||||
display: grid;
|
||||
}
|
||||
.NB-feature-group {
|
||||
border-left: 3px solid #0000;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.NB-feature-group-read {
|
||||
border-left-color: #c0a06a;
|
||||
}
|
||||
.NB-feature-group-smart {
|
||||
border-left-color: #7a9a5a;
|
||||
}
|
||||
.NB-feature-group-share {
|
||||
border-left-color: #8a6a9a;
|
||||
}
|
||||
.NB-feature-group-everywhere {
|
||||
border-left-color: #5a8aaa;
|
||||
}
|
||||
.NB-feature-group-label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin: 0 0 14px;
|
||||
padding: 0;
|
||||
font-family:
|
||||
Gotham Narrow A,
|
||||
Gotham Narrow B,
|
||||
sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.NB-feature-group-read .NB-feature-group-label {
|
||||
color: #b0903a;
|
||||
}
|
||||
.NB-feature-group-smart .NB-feature-group-label {
|
||||
color: #5a7a3a;
|
||||
}
|
||||
.NB-feature-group-share .NB-feature-group-label {
|
||||
color: #7a5a8a;
|
||||
}
|
||||
.NB-feature-group-everywhere .NB-feature-group-label {
|
||||
color: #4a7a9a;
|
||||
}
|
||||
.NB-feature-group-items {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
display: flex;
|
||||
}
|
||||
a.NB-fgi {
|
||||
color: inherit;
|
||||
border-radius: 8px;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 10px 12px 10px 0;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.15s;
|
||||
display: flex;
|
||||
}
|
||||
a.NB-fgi img {
|
||||
object-fit: cover;
|
||||
object-position: left top;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
box-shadow: 0 1px 4px #00000026;
|
||||
}
|
||||
.NB-fgi-text {
|
||||
min-width: 0;
|
||||
}
|
||||
.NB-fgi-caption {
|
||||
color: #3a2e1e;
|
||||
font-family:
|
||||
Gotham Narrow A,
|
||||
Gotham Narrow B,
|
||||
sans-serif;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.NB-fgi-desc {
|
||||
color: #8a7a6a;
|
||||
margin-top: 4px;
|
||||
font-size: 16px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="NB-welcome NB-premium NB-is-premium NB-is-anonymous NB-pref-story-position-center NB-dashboard-columns-double NB-pref-sticky-story-sideoptions NB-content-preview-1 NB-intelligence-neutral NB-feedlist-hide-read-feeds NB-theme-whitney NB-theme-size-m NB-theme-feed-size-m NB-theme-feed-font-whitney NB-line-spacing-m NB-density-comfortable NB-image-preview-small-right"
|
||||
style="background-color: rgb(26, 37, 37)"
|
||||
>
|
||||
<div class="NB-body-inner">
|
||||
<div class="NB-welcome-container NB-splash">
|
||||
<div class="NB-welcome-features-wrap">
|
||||
<div class="NB-welcome-feature-groups">
|
||||
<div class="NB-inner">
|
||||
<div class="NB-feature-group NB-feature-group-read">
|
||||
<h3 class="NB-feature-group-label">Read Your Way</h3>
|
||||
<div class="NB-feature-group-items">
|
||||
<a href="https://newsblur.com/features#speed" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/feature_1.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Real-time RSS</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Stories are pushed directly to you, so you can read news
|
||||
as it comes in
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#original"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/feature_2.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Original Site</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Read the content in context, the way it was meant to be
|
||||
seen
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#text-view"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_3.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Read the Full Story</div>
|
||||
<div class="NB-fgi-desc">
|
||||
The original story from truncated RSS feeds is
|
||||
seamlessly expanded
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#layouts"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_8.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">
|
||||
Grid, List, Split, Magazine
|
||||
</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Every site has its own story layout, so you can mix and
|
||||
match
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://newsblur.com/features#themes" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/subfeature_9.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Dark Mode</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Easy on the eyes and built into the web, iOS, and
|
||||
Android
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://newsblur.com/features#river" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/subfeature_16.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">River of News</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Read all your feeds by folder in one scrollable stream
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#shortcuts"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_15.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Keyboard Shortcuts</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Navigate everything without touching the mouse
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="NB-feature-group NB-feature-group-smart">
|
||||
<h3 class="NB-feature-group-label">Stay Smart</h3>
|
||||
<div class="NB-feature-group-items">
|
||||
<a
|
||||
href="https://newsblur.com/features#training"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/feature_4.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Intelligence Training</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Hide the stories you don't like and highlight the
|
||||
stories you do
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://newsblur.com/features#search" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/feature_5.jpg" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Full Text Search</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Quickly find stories across all of your subscriptions
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://newsblur.com/features#search" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/subfeature_1.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Saved Searches</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Regularly used searches are conveniently given their own
|
||||
feeds
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://newsblur.com/features#ask-ai" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/subfeature_12.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Ask AI</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Ask questions about any story and get answers powered by
|
||||
AI
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#briefing"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_13.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Daily Briefing</div>
|
||||
<div class="NB-fgi-desc">
|
||||
A summary of your top stories, customizable and
|
||||
delivered every day
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#changes"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_5.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Track Changes</div>
|
||||
<div class="NB-fgi-desc">
|
||||
See how a story evolved since it was first published
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#notifications"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_14.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Notifications</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Get alerted the moment a feed publishes, on web, iOS, or
|
||||
Android
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="NB-feature-group NB-feature-group-share">
|
||||
<h3 class="NB-feature-group-label">Share & Organize</h3>
|
||||
<div class="NB-feature-group-items">
|
||||
<a
|
||||
href="https://newsblur.com/features#blurblog"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/feature_3.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Shared Stories</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Reading news is better with friends. Share stories on
|
||||
your public blurblog
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#blurblog"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/feature_7.jpg" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Blurblog Privacy</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Share stories with the world or only with your friends
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://newsblur.com/features#saved" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/feature_6.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Story Tagging</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Save stories with custom tags for fast references
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#newsletters"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_7.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Email Newsletters</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Read your email newsletters where they belong, in a news
|
||||
reader
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="NB-feature-group NB-feature-group-everywhere">
|
||||
<h3 class="NB-feature-group-label">Works Everywhere</h3>
|
||||
<div class="NB-feature-group-items">
|
||||
<a href="https://newsblur.com/features#apps" class="NB-fgi">
|
||||
<img src="./grid_newsblur_files/feature_8.jpg" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Third-party Apps</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Supports Reeder, ReadKit, Unread, and loads more
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#integrations"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_6.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">IFTTT & Zapier</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Hook NewsBlur up to nearly every service on the web
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#web-feeds"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_10.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">YouTube</div>
|
||||
<div class="NB-fgi-desc">
|
||||
Even sites that don't publish RSS feeds can be followed
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://newsblur.com/features#web-feeds"
|
||||
class="NB-fgi"
|
||||
>
|
||||
<img src="./grid_newsblur_files/subfeature_11.png" />
|
||||
<div class="NB-fgi-text">
|
||||
<div class="NB-fgi-caption">Web Feeds</div>
|
||||
<div class="NB-fgi-desc">
|
||||
A creative feed for any website, even without RSS
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -268,6 +268,22 @@ void FlexLayouter::measureFlexItems( const Axis& mainAxis, const Axis& crossAxis
|
||||
item.widget->setLayoutWidthPolicy( SizePolicy::WrapContent );
|
||||
else
|
||||
item.widget->setLayoutHeightPolicy( SizePolicy::WrapContent );
|
||||
|
||||
// Set the item's internal size to the container's main size,
|
||||
// so the block layouter's `else` branch (non-WrapContent)
|
||||
// wraps text at the full container width. The flex-shrink
|
||||
// step will then proportionally reduce the item to its final
|
||||
// size, giving the expected "fill remaining space" behaviour.
|
||||
Float containerMainDim =
|
||||
mainAxis.horizontal
|
||||
? containerWidth - containerPadding.Left - containerPadding.Right
|
||||
: containerHeight - containerPadding.Top - containerPadding.Bottom;
|
||||
if ( containerMainDim > 0.f ) {
|
||||
if ( mainAxis.horizontal )
|
||||
item.widget->setInternalPixelsWidth( containerMainDim );
|
||||
else
|
||||
item.widget->setInternalPixelsHeight( containerMainDim );
|
||||
}
|
||||
}
|
||||
|
||||
if ( item.widget->isType( UI_TYPE_HTML_WIDGET ) ) {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/css/stylesheetlength.hpp>
|
||||
#include <eepp/ui/css/stylesheetproperty.hpp>
|
||||
#include <eepp/ui/css/stylesheetspecification.hpp>
|
||||
#include <eepp/ui/gridlayouter.hpp>
|
||||
#include <eepp/ui/uihtmlwidget.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
#include <eepp/ui/uitextnode.hpp>
|
||||
#include <set>
|
||||
|
||||
@@ -747,8 +752,12 @@ void GridLayouter::preSizeItemsForRowSizing() {
|
||||
if ( item.widget->isType( UI_TYPE_HTML_WIDGET ) ) {
|
||||
auto* childHtml = item.widget->asType<UIHTMLWidget>();
|
||||
auto* layouter = childHtml->getLayouter();
|
||||
if ( layouter && !layouter->isPacking() )
|
||||
if ( layouter && !layouter->isPacking() ) {
|
||||
auto oldHP = childHtml->getLayoutHeightPolicy();
|
||||
childHtml->setLayoutHeightPolicy( SizePolicy::WrapContent );
|
||||
childHtml->updateLayout();
|
||||
childHtml->setLayoutHeightPolicy( oldHP );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -944,11 +953,70 @@ void GridLayouter::updateLayout() {
|
||||
autoPlaceItems();
|
||||
buildImplicitTracks();
|
||||
collapseEmptyAutoFitTracks();
|
||||
sizeTracksForAxis( true );
|
||||
// Pre-size items to their column widths so row sizing sees correct heights
|
||||
preSizeItemsForRowSizing();
|
||||
sizeTracksForAxis( false );
|
||||
applyLayout();
|
||||
|
||||
int needResize = true;
|
||||
bool hPercentUnresolved = false;
|
||||
bool wPercentUnresolved = false;
|
||||
if ( grid && grid->getUIStyle() ) {
|
||||
const StyleSheetProperty* hprop = grid->getUIStyle()->getProperty( PropertyId::Height );
|
||||
hPercentUnresolved =
|
||||
hprop && StyleSheetLength::isPercentage( hprop->value() ) &&
|
||||
grid->getPixelsSize().getHeight() <= 0.f;
|
||||
const StyleSheetProperty* wprop = grid->getUIStyle()->getProperty( PropertyId::Width );
|
||||
wPercentUnresolved =
|
||||
wprop && StyleSheetLength::isPercentage( wprop->value() ) &&
|
||||
grid->getPixelsSize().getWidth() <= 0.f;
|
||||
}
|
||||
for ( int sizingPass = 0; sizingPass < 2 && needResize; ++sizingPass ) {
|
||||
needResize = false;
|
||||
sizeTracksForAxis( true );
|
||||
preSizeItemsForRowSizing();
|
||||
sizeTracksForAxis( false );
|
||||
applyLayout();
|
||||
|
||||
if ( hPercentUnresolved ) {
|
||||
Float contentH = 0.f;
|
||||
for ( const auto& row : mRows )
|
||||
contentH += row.baseSize;
|
||||
if ( mRows.size() > 1 )
|
||||
contentH += static_cast<Float>( mRows.size() - 1 ) * mRowGap;
|
||||
contentH +=
|
||||
mContainer->getPixelsContentOffset().Top +
|
||||
mContainer->getPixelsContentOffset().Bottom;
|
||||
if ( contentH > 0.f ) {
|
||||
mContainer->setInternalPixelsHeight( contentH );
|
||||
needResize = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( wPercentUnresolved ) {
|
||||
const StyleSheetProperty* wprop =
|
||||
grid->getUIStyle()->getProperty( PropertyId::Width );
|
||||
std::string v = wprop->value();
|
||||
v.pop_back();
|
||||
Float pct = 0.f;
|
||||
String::fromString( pct, v );
|
||||
Float best = 0.f;
|
||||
for ( Node* anc = grid->getParent(); anc; anc = anc->getParent() ) {
|
||||
if ( anc->isWidget() ) {
|
||||
Float sz = anc->asType<UIWidget>()->getPixelsSize().getWidth();
|
||||
if ( sz > best )
|
||||
best = sz;
|
||||
}
|
||||
}
|
||||
UISceneNode* root = grid->getUISceneNode();
|
||||
if ( root ) {
|
||||
Float sz = root->getPixelsSize().getWidth();
|
||||
if ( sz > best )
|
||||
best = sz;
|
||||
}
|
||||
Float resolved = best * pct / 100.f;
|
||||
if ( resolved > 0.f ) {
|
||||
mContainer->setInternalPixelsWidth( resolved );
|
||||
needResize = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 15: set paint-order sort flag
|
||||
if ( grid ) {
|
||||
@@ -1097,9 +1165,6 @@ GridLineResult GridLineParser::parse( const std::string& value ) {
|
||||
|
||||
static void resolveAxisPlacement( const GridLineResult& start, const GridLineResult& end,
|
||||
int explicitCount, int& outStart, int& outEnd ) {
|
||||
outStart = 0;
|
||||
outEnd = 0;
|
||||
|
||||
if ( start.isAuto && end.isAuto )
|
||||
return;
|
||||
|
||||
@@ -1177,8 +1242,10 @@ static void resolveAxisPlacement( const GridLineResult& start, const GridLineRes
|
||||
}
|
||||
|
||||
void GridLayouter::resolveDefinitePlacements() {
|
||||
int explicitCols = static_cast<int>( mColumns.size() );
|
||||
int explicitRows = static_cast<int>( mRows.size() );
|
||||
int explicitCols =
|
||||
std::max( static_cast<int>( mExplicitColCount ), static_cast<int>( mColumns.size() ) );
|
||||
int explicitRows =
|
||||
std::max( static_cast<int>( mExplicitRowCount ), static_cast<int>( mRows.size() ) );
|
||||
|
||||
mMaxColumn = explicitCols;
|
||||
mMaxRow = explicitRows;
|
||||
@@ -1266,6 +1333,9 @@ void GridLayouter::resolveDefinitePlacements() {
|
||||
}
|
||||
|
||||
void GridLayouter::autoPlaceItems() {
|
||||
int maxColumns = mMaxColumn;
|
||||
int maxRows = mMaxRow;
|
||||
|
||||
std::set<std::pair<int, int>> occupied;
|
||||
for ( const auto& item : mItems ) {
|
||||
if ( item.resolvedColumnStart > 0 && item.resolvedRowStart > 0 ) {
|
||||
@@ -1332,7 +1402,7 @@ void GridLayouter::autoPlaceItems() {
|
||||
if ( mAutoFlowDense ) {
|
||||
// Dense: search from the beginning for first empty cell
|
||||
for ( int r = 1;; ++r ) {
|
||||
for ( int c = 1; c <= std::max( mMaxColumn, 1 ); ++c ) {
|
||||
for ( int c = 1; c <= std::max( maxColumns, 1 ); ++c ) {
|
||||
if ( fits( r, c, sw, sh ) ) {
|
||||
item.resolvedRowStart = r;
|
||||
item.resolvedRowEnd = r + sh;
|
||||
@@ -1351,7 +1421,7 @@ void GridLayouter::autoPlaceItems() {
|
||||
} else {
|
||||
for ( int r = cursorRow;; ++r ) {
|
||||
int startC = ( r == cursorRow ) ? cursorCol : 1;
|
||||
for ( int c = startC; c <= std::max( mMaxColumn, 1 ); ++c ) {
|
||||
for ( int c = startC; c <= std::max( maxColumns, 1 ); ++c ) {
|
||||
if ( fits( r, c, sw, sh ) ) {
|
||||
item.resolvedRowStart = r;
|
||||
item.resolvedRowEnd = r + sh;
|
||||
@@ -1373,7 +1443,7 @@ void GridLayouter::autoPlaceItems() {
|
||||
bool placed = false;
|
||||
if ( mAutoFlowDense ) {
|
||||
for ( int c = 1;; ++c ) {
|
||||
for ( int r = 1; r <= std::max( mMaxRow, 1 ); ++r ) {
|
||||
for ( int r = 1; r <= std::max( maxRows, 1 ); ++r ) {
|
||||
if ( fits( r, c, sw, sh ) ) {
|
||||
item.resolvedRowStart = r;
|
||||
item.resolvedRowEnd = r + sh;
|
||||
@@ -1392,7 +1462,7 @@ void GridLayouter::autoPlaceItems() {
|
||||
} else {
|
||||
for ( int c = cursorCol;; ++c ) {
|
||||
int startR = ( c == cursorCol ) ? cursorRow : 1;
|
||||
int rowLimit = std::max( mMaxRow, 1 );
|
||||
int rowLimit = std::max( maxRows, 1 );
|
||||
for ( int r = startR; r <= rowLimit; ++r ) {
|
||||
if ( fits( r, c, sw, sh ) ) {
|
||||
item.resolvedRowStart = r;
|
||||
@@ -1413,6 +1483,9 @@ void GridLayouter::autoPlaceItems() {
|
||||
}
|
||||
}
|
||||
|
||||
// Only update member fields — the local snapshot must NOT grow
|
||||
// during auto-placement, otherwise later items overflow into
|
||||
// implicit columns instead of wrapping to the next row.
|
||||
if ( item.resolvedColumnEnd > mMaxColumn )
|
||||
mMaxColumn = item.resolvedColumnEnd;
|
||||
if ( item.resolvedRowEnd > mMaxRow )
|
||||
|
||||
@@ -1716,3 +1716,41 @@ UTEST( GridContainer, downloadGridLayout ) {
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( GridContainer, newsblurReducedGrid ) {
|
||||
Engine::instance()->createWindow( WindowSettings( 1024, 650, "UIHTML Grid NewsBlur Test",
|
||||
WindowStyle::Default, WindowBackend::Default,
|
||||
32, {}, 1, false, true ),
|
||||
ContextSettings() );
|
||||
init_grid_test();
|
||||
UISceneNode* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" );
|
||||
std::string htmlContent;
|
||||
ASSERT_TRUE( FileSystem::fileGet( "assets/html/grid_newsblur_reduced.html", htmlContent ) );
|
||||
sceneNode->loadLayoutFromString( EE::UI::Tools::HTMLFormatter::HTMLtoXML( htmlContent ) );
|
||||
sceneNode->update( Seconds( 1 ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* gridEl = sceneNode->getRoot()->findByClass( "NB-inner" );
|
||||
ASSERT_TRUE( nullptr != gridEl );
|
||||
ASSERT_TRUE( gridEl->isWidget() );
|
||||
|
||||
Float gridW = gridEl->getPixelsSize().getWidth();
|
||||
Float gridH = gridEl->getPixelsSize().getHeight();
|
||||
EXPECT_GT( gridW, 100.f );
|
||||
EXPECT_GT( gridH, 100.f );
|
||||
|
||||
// Verify NB-feature-group items aren't overexpanded
|
||||
auto* fg = sceneNode->getRoot()->findByClass( "NB-feature-group" );
|
||||
ASSERT_TRUE( nullptr != fg );
|
||||
Float fgH = fg->getPixelsSize().getHeight();
|
||||
EXPECT_GT( fgH, 100.f );
|
||||
EXPECT_LT( fgH, 2000.f );
|
||||
|
||||
auto* fgiText = sceneNode->getRoot()->findByClass( "NB-fgi-text" );
|
||||
ASSERT_TRUE( nullptr != fgiText );
|
||||
Float textW = fgiText->getPixelsSize().getWidth();
|
||||
EXPECT_GT( textW, 150.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user