mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-07-15 23:42:52 +03:00
Grid layout WIP.
This commit is contained in:
@@ -860,3 +860,82 @@ Do not checkpoint a failing phase as complete.
|
||||
- Existing flex, table, block, inline, and out-of-flow layout tests still pass.
|
||||
- `.agent/rules/html-layout-architecture.md` is updated with `GridLayouter` support and any
|
||||
intentional limitations.
|
||||
|
||||
## Bug Tracking
|
||||
|
||||
### FIXED (34)
|
||||
|
||||
| # | Bug | Test |
|
||||
|---|-----|------|
|
||||
| 1 | Stack overflow: `setPixelsSize` on container → re-layout cycle | `UIHTML.GridDownloadLayout` |
|
||||
| 2 | Missing `mPacking` guard + transaction | `UIHTML.GridDownloadLayout` |
|
||||
| 3 | `parseTrackList` didn't propagate `hasAutoRepeat`/`autoRepeatTemplate` | `GridParser.autoRepeatExpand` |
|
||||
| 4 | `expandAutoRepeat` count from max only (ignored `minmax` min) | `GridContainer.autoFitColumnCount` |
|
||||
| 5 | `expandAutoRepeat` never called from `readContainerStyle` | `GridContainer.autoFitColumnCount` |
|
||||
| 6 | Items filled parent width: `MatchParent` → fill containing block | `GridContainer.itemWidthStretch` |
|
||||
| 7 | Items uneven height: `WrapContent` height → each card had its own height | `GridContainer.itemHeightStretch` |
|
||||
| 8 | `sizeTracksForAxis` uses `mColumnGap` for both axes | `GridContainer.rowGapWorks` |
|
||||
| 9 | Unsigned underflow `tracks.size()-1` when empty | `GridContainer.emptyGridNoCrash` |
|
||||
| 10 | Multi-span `cellW`/`cellH` missing internal gaps | `GridContainer.spanningItemWidthIncludesGaps` |
|
||||
| 11 | `justify-content` space-between/around/evenly as uniform offset | `GridContainer.justifyContentSpaceBetween` |
|
||||
| 12 | `expandAutoRepeat` appends to end (wrong insertion pos) | `GridParser.autoRepeatPositionInsert` |
|
||||
| 13 | Row axis auto-fit collapse never implemented | `GridContainer.autoFitRowCount` |
|
||||
| 14 | Negative line overflow not clamped to 1 | `GridContainer.negativeLineClamped` |
|
||||
| 15 | P1: Negative `fr` values accepted (`-1fr`) | `GridParser.rejectNegativeFr` |
|
||||
| 16 | P2: `isNumber()` accepts malformed decimals `12.34.56` | (unit testable) |
|
||||
| 17 | P3: Nested auto-repeat in fixed-repeat silently drops tracks | (unit testable) |
|
||||
| 18 | P4: Auto-repeat zero-min tracks collapses to count=1 | `GridContainer.autoRepeatZeroMinFillsContainer` |
|
||||
| 19 | P7: Custom-idents starting with "span" misinterpreted | (unit testable) |
|
||||
| 20 | PL1+PL4: Span on definite axis ignored for semi-auto items | `GridContainer.definiteColumnSpanAutoRow` |
|
||||
| 21 | PL2: Dense mode missing in both-auto placement | `GridContainer.denseAutoPlacement` |
|
||||
| 22 | PL3: `colLimit` static throughout loop | (implicitly tested) |
|
||||
| 23 | PL5: Both span drops start span (now uses end span) | (unit testable) |
|
||||
| 24 | PL6: Named grid lines never resolved | (unit testable) |
|
||||
| 25 | TS1: Percentage `max` treated as infinite growth limit | (unit testable) |
|
||||
| 26 | TS2: `FitContent` max treated as infinite growth limit | (unit testable) |
|
||||
| 27 | TS4: Flex distribution never clamps to growth limits | `GridContainer.flexGrowthLimit` |
|
||||
| 28 | TS6: Spanning extra clamped to growth limit w/o redistribute | (unit testable) |
|
||||
| 29 | TS7: `grid-auto-rows`/`grid-auto-columns` properties ignored | `GridContainer.implicitTrackSizing` |
|
||||
| 30 | I1: `computeIntrinsicWidths` ignores auto-repeat tracks | `GridContainer.intrinsicWidthAutoRepeat` |
|
||||
| 31 | I2: `computeIntrinsicWidths` uses uninitialized `mColumnGap` | `GridContainer.intrinsicWidthAutoRepeat` |
|
||||
| 32 | L2: `collapseEmptyAutoFitTracks` may remove implicit tracks | (unit testable) |
|
||||
| 33 | L3: Track sizing adds gaps for content but cells omit them | `GridContainer.spanningItemWidthIncludesGaps` |
|
||||
| 34 | TS3: Content-based `max` ignored for `minmax(fixed, auto)` | (implicitly fixed) |
|
||||
|
||||
### NOT FIXED (9)
|
||||
|
||||
#### Parser (2)
|
||||
|
||||
| # | Sev | Bug | File:Line |
|
||||
|---|-----|-----|-----------|
|
||||
| P5 | L | Invalid tokens silently become `Length(0)` | `gridlayouter.cpp:112-122` |
|
||||
| P6 | L | Malformed bracket tokens flow to function parsing | `gridlayouter.cpp:265-271` |
|
||||
|
||||
#### Layout (3)
|
||||
|
||||
| # | Sev | Bug | File:Line |
|
||||
|---|-----|-----|-----------|
|
||||
| L1 | H | `preSizeItemsForRowSizing` doesn't reflow content after width change | `gridlayouter.cpp:661-691` |
|
||||
| L4 | M | Baseline computed as bottom edge instead of text baseline | `gridlayouter.cpp:871-878` |
|
||||
| L5 | L | `mPacking` may silently skip needed re-layout | `gridlayouter.cpp:856` |
|
||||
|
||||
#### Track Sizing (1)
|
||||
|
||||
| # | Sev | Bug | File:Line |
|
||||
|---|-----|-----|-----------|
|
||||
| TS5 | L | Floating-point error from iterative subtraction in flex distribution | `gridlayouter.cpp:657-669` |
|
||||
|
||||
#### Intrinsic / Edge (3)
|
||||
|
||||
| # | Sev | Bug | File:Line |
|
||||
|---|-----|-----|-----------|
|
||||
| I3 | L | Auto-repeat expanded once with potentially zero container size | `gridlayouter.cpp:460-469` |
|
||||
| I4 | L | Span stored in `resolvedEnd` (overloaded, works but fragile) | `gridlayouter.cpp:1009-1024` |
|
||||
| I5 | L | Negative `contentBoxSize` not handled | `gridlayouter.cpp:486-492` |
|
||||
|
||||
### Stats
|
||||
|
||||
- **Total bugs found**: 43
|
||||
- **Fixed**: 34
|
||||
- **Unfixed**: 9
|
||||
- **By severity**: 1 High, 1 Medium, 7 Low (remaining)
|
||||
|
||||
217
bin/unit_tests/assets/html/grid_download.html
Normal file
217
bin/unit_tests/assets/html/grid_download.html
Normal file
@@ -0,0 +1,217 @@
|
||||
<!doctype html>
|
||||
<!-- saved from url=(0018)https://zsnes.com/ -->
|
||||
<html lang="en">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #1e2a38;
|
||||
font:
|
||||
13px Verdana,
|
||||
Sans-Serif;
|
||||
color: #d0d8e0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #60c0e0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #2a3a4a;
|
||||
color: #80d8f0;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 30px 20px 10px;
|
||||
}
|
||||
|
||||
header img.logo {
|
||||
max-width: 400px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: #162028;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
border-bottom: 1px solid #2a3a4a;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin: 0 10px;
|
||||
font:
|
||||
bold 11px Verdana,
|
||||
Sans-Serif;
|
||||
text-decoration: none;
|
||||
color: #80b8d0;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
background-color: #2a3a4a;
|
||||
color: #a0d8f0;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 960px;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
background-color: #162028;
|
||||
color: #60c0e0;
|
||||
font:
|
||||
bold 14px Arial,
|
||||
Sans-Serif;
|
||||
padding: 5px 8px;
|
||||
border-left: 1px solid #2a3a4a;
|
||||
border-top: 1px solid #2a3a4a;
|
||||
}
|
||||
|
||||
section p,
|
||||
section ul {
|
||||
background-color: #1a2838;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #2a3a4a;
|
||||
border-right: 1px solid #2a3a4a;
|
||||
}
|
||||
|
||||
section p {
|
||||
text-indent: 12px;
|
||||
}
|
||||
|
||||
section ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
section ul li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #e0b800;
|
||||
}
|
||||
|
||||
.download-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.download-card {
|
||||
background-color: #1a2838;
|
||||
border: 1px solid #2a3a4a;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.download-card h3 {
|
||||
font:
|
||||
bold 12px Verdana,
|
||||
Sans-Serif;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.download-card a {
|
||||
display: inline-block;
|
||||
padding: 6px 14px;
|
||||
background-color: #162028;
|
||||
color: #60c0e0;
|
||||
text-decoration: none;
|
||||
font:
|
||||
11px Verdana,
|
||||
Sans-Serif;
|
||||
border: 1px solid #2a3a4a;
|
||||
}
|
||||
|
||||
.download-card a:hover {
|
||||
background-color: #2a3a4a;
|
||||
color: #80d8f0;
|
||||
}
|
||||
|
||||
.download-card .pending {
|
||||
color: #8090a0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font:
|
||||
11px Verdana,
|
||||
Sans-Serif;
|
||||
color: #607080;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header img.logo {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: block;
|
||||
margin: 4px 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<section id="downloads">
|
||||
<h2>Downloads</h2>
|
||||
<div class="download-grid">
|
||||
<div class="download-card">
|
||||
<h3>Windows</h3>
|
||||
<a href="https://zsnes.com/files/SuperZSNES_v0.201.zip">Download</a>
|
||||
</div>
|
||||
<div class="download-card">
|
||||
<h3>Mac</h3>
|
||||
<a href="https://zsnes.com/files/SuperZSNES_v0.201.dmg">Download</a>
|
||||
</div>
|
||||
<div class="download-card">
|
||||
<h3>Linux</h3>
|
||||
<a href="https://zsnes.com/files/SuperZSNES_v0.201.tar.gz"
|
||||
>Download</a
|
||||
>
|
||||
</div>
|
||||
<div class="download-card">
|
||||
<h3>Android</h3>
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=com.zsnes.superzsnes"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Google Play</a
|
||||
>
|
||||
</div>
|
||||
<div class="download-card">
|
||||
<h3>iOS</h3>
|
||||
<span class="pending">Coming Soon</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -288,6 +288,23 @@ enum class PropertyId : Uint32 {
|
||||
RowGap = String::hash( "row-gap" ),
|
||||
ColumnGap = String::hash( "column-gap" ),
|
||||
Gap = String::hash( "gap" ),
|
||||
GridTemplateRows = String::hash( "grid-template-rows" ),
|
||||
GridTemplateColumns = String::hash( "grid-template-columns" ),
|
||||
GridTemplateAreas = String::hash( "grid-template-areas" ),
|
||||
GridTemplate = String::hash( "grid-template" ),
|
||||
GridAutoRows = String::hash( "grid-auto-rows" ),
|
||||
GridAutoColumns = String::hash( "grid-auto-columns" ),
|
||||
GridAutoFlow = String::hash( "grid-auto-flow" ),
|
||||
Grid = String::hash( "grid" ),
|
||||
GridRowStart = String::hash( "grid-row-start" ),
|
||||
GridRowEnd = String::hash( "grid-row-end" ),
|
||||
GridColumnStart = String::hash( "grid-column-start" ),
|
||||
GridColumnEnd = String::hash( "grid-column-end" ),
|
||||
GridRow = String::hash( "grid-row" ),
|
||||
GridColumn = String::hash( "grid-column" ),
|
||||
GridArea = String::hash( "grid-area" ),
|
||||
JustifyItems = String::hash( "justify-items" ),
|
||||
JustifySelf = String::hash( "justify-self" ),
|
||||
};
|
||||
|
||||
enum class PropertyType : Uint32 {
|
||||
|
||||
@@ -14,6 +14,8 @@ enum class CSSDisplay {
|
||||
ListItem,
|
||||
Flex,
|
||||
InlineFlex,
|
||||
Grid,
|
||||
InlineGrid,
|
||||
None,
|
||||
Table,
|
||||
TableRow,
|
||||
@@ -162,6 +164,27 @@ struct EE_API CSSBaselineAlignmentHelper {
|
||||
static CSSBaselineAlignValue fromKeyword( std::string_view val );
|
||||
};
|
||||
|
||||
enum class CSSGridAutoFlow { Row, Column };
|
||||
|
||||
struct EE_API CSSGridAutoFlowHelper {
|
||||
static std::string toString( CSSGridAutoFlow val );
|
||||
static CSSGridAutoFlow fromString( std::string_view val );
|
||||
};
|
||||
|
||||
enum class CSSJustifyItems { Normal, Stretch, Start, End, Center, FlexStart, FlexEnd };
|
||||
|
||||
struct EE_API CSSJustifyItemsHelper {
|
||||
static std::string toString( CSSJustifyItems val );
|
||||
static CSSJustifyItems fromString( std::string_view val );
|
||||
};
|
||||
|
||||
enum class CSSJustifySelf { Auto, Normal, Stretch, Start, End, Center, FlexStart, FlexEnd };
|
||||
|
||||
struct EE_API CSSJustifySelfHelper {
|
||||
static std::string toString( CSSJustifySelf val );
|
||||
static CSSJustifySelf fromString( std::string_view val );
|
||||
};
|
||||
|
||||
enum class CSSVisibility { Visible, Hidden, Collapse };
|
||||
|
||||
struct EE_API CSSVisibilityHelper {
|
||||
|
||||
170
include/eepp/ui/gridlayouter.hpp
Normal file
170
include/eepp/ui/gridlayouter.hpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#ifndef EE_UI_GRIDLAYOUTER_HPP
|
||||
#define EE_UI_GRIDLAYOUTER_HPP
|
||||
|
||||
#include <eepp/core/containers.hpp>
|
||||
#include <eepp/core/small_vector.hpp>
|
||||
#include <eepp/math/rect.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/csslayouttypes.hpp>
|
||||
#include <eepp/ui/uihelper.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
#include <limits>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UIWidget;
|
||||
class UIHTMLWidget;
|
||||
|
||||
// ── Structured grid track data model (used by GridLayouter and tests) ──
|
||||
|
||||
enum class GridTrackBreadthType {
|
||||
Length,
|
||||
Percentage,
|
||||
Flex,
|
||||
Auto,
|
||||
MinContent,
|
||||
MaxContent,
|
||||
FitContent
|
||||
};
|
||||
|
||||
struct EE_API GridTrackBreadth {
|
||||
GridTrackBreadthType type{ GridTrackBreadthType::Auto };
|
||||
Float value{ 0.f };
|
||||
std::string raw{ "auto" };
|
||||
bool valid{ true };
|
||||
};
|
||||
|
||||
struct EE_API GridTrackSize {
|
||||
GridTrackBreadth min;
|
||||
GridTrackBreadth max;
|
||||
};
|
||||
|
||||
struct EE_API GridExplicitTrack {
|
||||
SmallVector<std::string, 2> beforeLineNames;
|
||||
GridTrackSize size;
|
||||
};
|
||||
|
||||
struct EE_API GridTrackList {
|
||||
std::vector<GridExplicitTrack> tracks;
|
||||
bool none{ false };
|
||||
bool valid{ true };
|
||||
bool hasAutoRepeat{ false };
|
||||
bool autoRepeatIsFit{ false };
|
||||
size_t autoRepeatPosition{ 0 };
|
||||
std::vector<GridExplicitTrack> autoRepeatTemplate;
|
||||
};
|
||||
|
||||
struct EE_API GridTrackParser {
|
||||
static GridTrackList parseTrackList( const std::string& value );
|
||||
static bool isValidTrackList( const std::string& value );
|
||||
static void expandAutoRepeat( GridTrackList& list, Float availableSize, Float gap );
|
||||
};
|
||||
|
||||
struct EE_API GridTrack {
|
||||
GridTrackSize definition;
|
||||
Float baseSize{ 0.f };
|
||||
Float growthLimit{ std::numeric_limits<Float>::infinity() };
|
||||
bool isFlex{ false };
|
||||
};
|
||||
|
||||
struct EE_API GridItem {
|
||||
UIWidget* widget;
|
||||
int order{ 0 };
|
||||
std::string gridRowStart{ "auto" };
|
||||
std::string gridRowEnd{ "auto" };
|
||||
std::string gridColumnStart{ "auto" };
|
||||
std::string gridColumnEnd{ "auto" };
|
||||
int resolvedRowStart{ 0 }; // 0 = auto
|
||||
int resolvedRowEnd{ 0 };
|
||||
int resolvedColumnStart{ 0 };
|
||||
int resolvedColumnEnd{ 0 };
|
||||
CSSJustifySelf justifySelf{ CSSJustifySelf::Stretch };
|
||||
CSSAlignSelf alignSelf{ CSSAlignSelf::Stretch };
|
||||
bool isTextNode{ false };
|
||||
bool isOutOfFlow{ false };
|
||||
};
|
||||
|
||||
// ── Parser helper exposed for tests ──
|
||||
|
||||
struct EE_API GridAreaTemplate {
|
||||
struct Area {
|
||||
int startRow{ 1 };
|
||||
int startColumn{ 1 };
|
||||
int endRow{ 2 };
|
||||
int endColumn{ 2 };
|
||||
};
|
||||
UnorderedMap<std::string, Area> areas;
|
||||
bool valid{ true };
|
||||
bool none{ false };
|
||||
};
|
||||
|
||||
struct EE_API GridAreasParser {
|
||||
static GridAreaTemplate parseAreas( const std::string& value );
|
||||
static bool isValidAreas( const std::string& value );
|
||||
static std::vector<std::string> getLineNamesForRowLine( const GridAreaTemplate& tmpl, int line );
|
||||
static std::vector<std::string> getLineNamesForColumnLine( const GridAreaTemplate& tmpl,
|
||||
int line );
|
||||
};
|
||||
|
||||
struct EE_API GridLineResult {
|
||||
bool isAuto{ true };
|
||||
bool isSpan{ false };
|
||||
int lineNumber{ 0 };
|
||||
int spanCount{ 1 };
|
||||
std::string nameId;
|
||||
};
|
||||
|
||||
struct EE_API GridLineParser {
|
||||
static GridLineResult parse( const std::string& value );
|
||||
};
|
||||
|
||||
// ── Layouter ──
|
||||
|
||||
class EE_API GridLayouter : public UILayouter {
|
||||
public:
|
||||
GridLayouter( UIWidget* container ) : UILayouter( container ) {}
|
||||
|
||||
void updateLayout() override;
|
||||
void computeIntrinsicWidths() override;
|
||||
Float getMinIntrinsicWidth() override;
|
||||
Float getMaxIntrinsicWidth() override;
|
||||
|
||||
Float getBaseline() const { return mContainerBaseline; }
|
||||
|
||||
const SmallVector<GridItem, 16>& getItems() const { return mItems; }
|
||||
const std::vector<GridTrack>& getColumns() const { return mColumns; }
|
||||
|
||||
protected:
|
||||
void readContainerStyle();
|
||||
void collectGridItems();
|
||||
void resolveDefinitePlacements();
|
||||
void autoPlaceItems();
|
||||
void buildImplicitTracks();
|
||||
void collapseEmptyAutoFitTracks();
|
||||
void preSizeItemsForRowSizing();
|
||||
void sizeTracksForAxis( bool isColumns );
|
||||
void applyLayout();
|
||||
|
||||
SmallVector<GridItem, 16> mItems;
|
||||
std::vector<GridTrack> mColumns;
|
||||
std::vector<GridTrack> mRows;
|
||||
GridAreaTemplate mTemplateAreas;
|
||||
Float mColumnGap{ 0.f };
|
||||
Float mRowGap{ 0.f };
|
||||
Float mContainerBaseline{ 0.f };
|
||||
int mMaxColumn{ 0 };
|
||||
int mMaxRow{ 0 };
|
||||
bool mAutoFlowDense{ false };
|
||||
bool mColAutoRepeatIsFit{ false };
|
||||
bool mRowAutoRepeatIsFit{ false };
|
||||
GridTrackSize mAutoColumnSize;
|
||||
GridTrackSize mAutoRowSize;
|
||||
size_t mExplicitColCount{ 0 };
|
||||
size_t mExplicitRowCount{ 0 };
|
||||
UnorderedMap<std::string, std::vector<int>> mColLineNames;
|
||||
UnorderedMap<std::string, std::vector<int>> mRowLineNames;
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
@@ -27,6 +27,23 @@ struct UIHTMLWidgetFlexState {
|
||||
std::string columnGap{ "0" };
|
||||
};
|
||||
|
||||
struct UIHTMLWidgetGridState {
|
||||
std::string templateRows{ "none" };
|
||||
std::string templateColumns{ "none" };
|
||||
std::string templateAreas{ "none" };
|
||||
std::string autoRows{ "auto" };
|
||||
std::string autoColumns{ "auto" };
|
||||
CSSGridAutoFlow autoFlow{ CSSGridAutoFlow::Row };
|
||||
bool autoFlowDense{ false };
|
||||
std::string rowStart{ "auto" };
|
||||
std::string rowEnd{ "auto" };
|
||||
std::string columnStart{ "auto" };
|
||||
std::string columnEnd{ "auto" };
|
||||
std::string area{ "auto" };
|
||||
CSSJustifyItems justifyItems{ CSSJustifyItems::Normal };
|
||||
CSSJustifySelf justifySelf{ CSSJustifySelf::Auto };
|
||||
};
|
||||
|
||||
class EE_API UIHTMLWidget : public UILayout {
|
||||
public:
|
||||
static UIHTMLWidget* New();
|
||||
@@ -51,6 +68,8 @@ class EE_API UIHTMLWidget : public UILayout {
|
||||
|
||||
bool isFlex() const;
|
||||
|
||||
bool isGrid() const;
|
||||
|
||||
CSSPosition getCSSPosition() const { return mPosition; }
|
||||
|
||||
void setCSSPosition( CSSPosition position );
|
||||
@@ -89,6 +108,14 @@ class EE_API UIHTMLWidget : public UILayout {
|
||||
return mFlexState;
|
||||
}
|
||||
|
||||
UIHTMLWidgetGridState* getGridState() const { return mGridState; }
|
||||
|
||||
UIHTMLWidgetGridState* ensureGridState() {
|
||||
if ( !mGridState )
|
||||
mGridState = eeNew( UIHTMLWidgetGridState, () );
|
||||
return mGridState;
|
||||
}
|
||||
|
||||
CSSFlexDirection getFlexDirection() const {
|
||||
return mFlexState ? mFlexState->direction : CSSFlexDirection::Row;
|
||||
}
|
||||
@@ -135,6 +162,84 @@ class EE_API UIHTMLWidget : public UILayout {
|
||||
int getOrder() const { return mFlexState ? mFlexState->order : 0; }
|
||||
void setOrder( int val );
|
||||
|
||||
const std::string& getGridTemplateRows() const {
|
||||
static const std::string sDefault( "none" );
|
||||
return mGridState ? mGridState->templateRows : sDefault;
|
||||
}
|
||||
void setGridTemplateRows( const std::string& val );
|
||||
|
||||
const std::string& getGridTemplateColumns() const {
|
||||
static const std::string sDefault( "none" );
|
||||
return mGridState ? mGridState->templateColumns : sDefault;
|
||||
}
|
||||
void setGridTemplateColumns( const std::string& val );
|
||||
|
||||
const std::string& getGridTemplateAreas() const {
|
||||
static const std::string sDefault( "none" );
|
||||
return mGridState ? mGridState->templateAreas : sDefault;
|
||||
}
|
||||
void setGridTemplateAreas( const std::string& val );
|
||||
|
||||
const std::string& getGridAutoRows() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->autoRows : sDefault;
|
||||
}
|
||||
void setGridAutoRows( const std::string& val );
|
||||
|
||||
const std::string& getGridAutoColumns() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->autoColumns : sDefault;
|
||||
}
|
||||
void setGridAutoColumns( const std::string& val );
|
||||
|
||||
CSSGridAutoFlow getGridAutoFlow() const {
|
||||
return mGridState ? mGridState->autoFlow : CSSGridAutoFlow::Row;
|
||||
}
|
||||
void setGridAutoFlow( CSSGridAutoFlow val );
|
||||
|
||||
bool getGridAutoFlowDense() const { return mGridState ? mGridState->autoFlowDense : false; }
|
||||
void setGridAutoFlowDense( bool val );
|
||||
|
||||
const std::string& getGridRowStart() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->rowStart : sDefault;
|
||||
}
|
||||
void setGridRowStart( const std::string& val );
|
||||
|
||||
const std::string& getGridRowEnd() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->rowEnd : sDefault;
|
||||
}
|
||||
void setGridRowEnd( const std::string& val );
|
||||
|
||||
const std::string& getGridColumnStart() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->columnStart : sDefault;
|
||||
}
|
||||
void setGridColumnStart( const std::string& val );
|
||||
|
||||
const std::string& getGridColumnEnd() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->columnEnd : sDefault;
|
||||
}
|
||||
void setGridColumnEnd( const std::string& val );
|
||||
|
||||
const std::string& getGridArea() const {
|
||||
static const std::string sDefault( "auto" );
|
||||
return mGridState ? mGridState->area : sDefault;
|
||||
}
|
||||
void setGridArea( const std::string& val );
|
||||
|
||||
CSSJustifyItems getJustifyItems() const {
|
||||
return mGridState ? mGridState->justifyItems : CSSJustifyItems::Normal;
|
||||
}
|
||||
void setJustifyItems( CSSJustifyItems val );
|
||||
|
||||
CSSJustifySelf getJustifySelf() const {
|
||||
return mGridState ? mGridState->justifySelf : CSSJustifySelf::Auto;
|
||||
}
|
||||
void setJustifySelf( CSSJustifySelf val );
|
||||
|
||||
const std::string& getRowGap() const {
|
||||
static const std::string sDefault( "0" );
|
||||
return mFlexState ? mFlexState->rowGap : sDefault;
|
||||
@@ -220,6 +325,7 @@ class EE_API UIHTMLWidget : public UILayout {
|
||||
bool mNeedsOrderSort{ false };
|
||||
UILayouter* mLayouter{ nullptr };
|
||||
UIHTMLWidgetFlexState* mFlexState{ nullptr };
|
||||
UIHTMLWidgetGridState* mGridState{ nullptr };
|
||||
UnorderedMap<std::string, StyleSheetProperty> mDataProperties;
|
||||
|
||||
Uint32 mScrollCb{ 0 };
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class GridLayouter;
|
||||
|
||||
class EE_API UILayout : public UIWidget {
|
||||
public:
|
||||
virtual Uint32 getType() const;
|
||||
@@ -28,6 +30,7 @@ class EE_API UILayout : public UIWidget {
|
||||
void setLayoutDirty();
|
||||
|
||||
protected:
|
||||
friend class GridLayouter;
|
||||
friend class UISceneNode;
|
||||
friend class UILayouter;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class EE_API UINode : public Node {
|
||||
public:
|
||||
friend class BlockLayouter;
|
||||
friend class FlexLayouter;
|
||||
friend class GridLayouter;
|
||||
friend class InlineLayouter;
|
||||
friend class TableLayouter;
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,10 @@ std::string CSSDisplayHelper::toString( CSSDisplay display ) {
|
||||
return "flex";
|
||||
case CSSDisplay::InlineFlex:
|
||||
return "inline-flex";
|
||||
case CSSDisplay::Grid:
|
||||
return "grid";
|
||||
case CSSDisplay::InlineGrid:
|
||||
return "inline-grid";
|
||||
case CSSDisplay::None:
|
||||
return "none";
|
||||
case CSSDisplay::Table:
|
||||
@@ -61,6 +65,10 @@ CSSDisplay CSSDisplayHelper::fromString( std::string_view val ) {
|
||||
display = CSSDisplay::Flex;
|
||||
else if ( val == "inline-flex" )
|
||||
display = CSSDisplay::InlineFlex;
|
||||
else if ( val == "grid" )
|
||||
display = CSSDisplay::Grid;
|
||||
else if ( val == "inline-grid" )
|
||||
display = CSSDisplay::InlineGrid;
|
||||
return display;
|
||||
}
|
||||
|
||||
@@ -444,6 +452,98 @@ CSSAlignSelf CSSAlignSelfHelper::fromString( std::string_view val ) {
|
||||
return CSSAlignSelf::Auto;
|
||||
}
|
||||
|
||||
std::string CSSGridAutoFlowHelper::toString( CSSGridAutoFlow val ) {
|
||||
switch ( val ) {
|
||||
case CSSGridAutoFlow::Column:
|
||||
return "column";
|
||||
case CSSGridAutoFlow::Row:
|
||||
default:
|
||||
return "row";
|
||||
}
|
||||
}
|
||||
|
||||
CSSGridAutoFlow CSSGridAutoFlowHelper::fromString( std::string_view val ) {
|
||||
if ( val == "column" )
|
||||
return CSSGridAutoFlow::Column;
|
||||
return CSSGridAutoFlow::Row;
|
||||
}
|
||||
|
||||
std::string CSSJustifyItemsHelper::toString( CSSJustifyItems val ) {
|
||||
switch ( val ) {
|
||||
case CSSJustifyItems::Stretch:
|
||||
return "stretch";
|
||||
case CSSJustifyItems::Start:
|
||||
return "start";
|
||||
case CSSJustifyItems::End:
|
||||
return "end";
|
||||
case CSSJustifyItems::Center:
|
||||
return "center";
|
||||
case CSSJustifyItems::FlexStart:
|
||||
return "flex-start";
|
||||
case CSSJustifyItems::FlexEnd:
|
||||
return "flex-end";
|
||||
case CSSJustifyItems::Normal:
|
||||
default:
|
||||
return "normal";
|
||||
}
|
||||
}
|
||||
|
||||
CSSJustifyItems CSSJustifyItemsHelper::fromString( std::string_view val ) {
|
||||
if ( val == "stretch" )
|
||||
return CSSJustifyItems::Stretch;
|
||||
if ( val == "start" )
|
||||
return CSSJustifyItems::Start;
|
||||
if ( val == "end" )
|
||||
return CSSJustifyItems::End;
|
||||
if ( val == "center" )
|
||||
return CSSJustifyItems::Center;
|
||||
if ( val == "flex-start" )
|
||||
return CSSJustifyItems::FlexStart;
|
||||
if ( val == "flex-end" )
|
||||
return CSSJustifyItems::FlexEnd;
|
||||
return CSSJustifyItems::Normal;
|
||||
}
|
||||
|
||||
std::string CSSJustifySelfHelper::toString( CSSJustifySelf val ) {
|
||||
switch ( val ) {
|
||||
case CSSJustifySelf::Stretch:
|
||||
return "stretch";
|
||||
case CSSJustifySelf::Start:
|
||||
return "start";
|
||||
case CSSJustifySelf::End:
|
||||
return "end";
|
||||
case CSSJustifySelf::Center:
|
||||
return "center";
|
||||
case CSSJustifySelf::FlexStart:
|
||||
return "flex-start";
|
||||
case CSSJustifySelf::FlexEnd:
|
||||
return "flex-end";
|
||||
case CSSJustifySelf::Normal:
|
||||
return "normal";
|
||||
case CSSJustifySelf::Auto:
|
||||
default:
|
||||
return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
CSSJustifySelf CSSJustifySelfHelper::fromString( std::string_view val ) {
|
||||
if ( val == "stretch" )
|
||||
return CSSJustifySelf::Stretch;
|
||||
if ( val == "start" )
|
||||
return CSSJustifySelf::Start;
|
||||
if ( val == "end" )
|
||||
return CSSJustifySelf::End;
|
||||
if ( val == "center" )
|
||||
return CSSJustifySelf::Center;
|
||||
if ( val == "flex-start" )
|
||||
return CSSJustifySelf::FlexStart;
|
||||
if ( val == "flex-end" )
|
||||
return CSSJustifySelf::FlexEnd;
|
||||
if ( val == "normal" )
|
||||
return CSSJustifySelf::Normal;
|
||||
return CSSJustifySelf::Auto;
|
||||
}
|
||||
|
||||
std::string CSSVisibilityHelper::toString( CSSVisibility val ) {
|
||||
switch ( val ) {
|
||||
case CSSVisibility::Visible:
|
||||
|
||||
@@ -511,6 +511,22 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
registerProperty( "row-gap", "0px" ).setType( PropertyType::NumberLength );
|
||||
registerProperty( "column-gap", "0px" ).setType( PropertyType::NumberLength );
|
||||
|
||||
registerProperty( "grid-template-rows", "none" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-template-columns", "none" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-template-areas", "none" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-auto-rows", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-auto-columns", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-auto-flow", "row" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-row-start", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-row-end", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-column-start", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-column-end", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-row", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-column", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "grid-area", "auto" ).setType( PropertyType::String );
|
||||
registerProperty( "justify-items", "normal" ).setType( PropertyType::String );
|
||||
registerProperty( "justify-self", "auto" ).setType( PropertyType::String );
|
||||
|
||||
// Shorthands
|
||||
registerShorthand( "margin", { "margin-top", "margin-right", "margin-bottom", "margin-left" },
|
||||
"box" );
|
||||
@@ -584,6 +600,16 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
registerShorthand( "flex-flow", { "flex-direction", "flex-wrap" }, "single-value-vector" );
|
||||
registerShorthand( "flex", { "flex-grow", "flex-shrink", "flex-basis" }, "flex" );
|
||||
registerShorthand( "gap", { "row-gap", "column-gap" }, "vector2" );
|
||||
registerShorthand( "grid-template",
|
||||
{ "grid-template-rows", "grid-template-columns", "grid-template-areas" },
|
||||
"grid-template" );
|
||||
registerShorthand( "grid",
|
||||
{ "grid-template-rows", "grid-template-columns", "grid-template-areas",
|
||||
"grid-auto-rows", "grid-auto-columns", "grid-auto-flow" },
|
||||
"grid" );
|
||||
registerShorthand( "place-items", { "align-items", "justify-items" }, "vector2" );
|
||||
registerShorthand( "place-self", { "align-self", "justify-self" }, "vector2" );
|
||||
registerShorthand( "place-content", { "align-content", "justify-content" }, "vector2" );
|
||||
}
|
||||
|
||||
void StyleSheetSpecification::registerNodeSelector( const std::string& name,
|
||||
@@ -1654,6 +1680,33 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
|
||||
|
||||
return properties;
|
||||
};
|
||||
|
||||
mShorthandParsers["grid-template"] =
|
||||
[]( const ShorthandDefinition* shorthand,
|
||||
std::string value ) -> std::vector<StyleSheetProperty> {
|
||||
value = String::trim( value );
|
||||
const auto& props = shorthand->getProperties();
|
||||
if ( value == "none" )
|
||||
return { StyleSheetProperty( props[0], "none" ), StyleSheetProperty( props[1], "none" ),
|
||||
StyleSheetProperty( props[2], "none" ) };
|
||||
|
||||
// Split on '/' for rows vs columns
|
||||
size_t slashPos = value.find( '/' );
|
||||
std::string rowsAndAreas = String::trim( value.substr( 0, slashPos ) );
|
||||
std::string cols = ( slashPos != std::string::npos )
|
||||
? String::trim( value.substr( slashPos + 1 ) )
|
||||
: "none";
|
||||
return { StyleSheetProperty( props[0], rowsAndAreas ), StyleSheetProperty( props[1], cols ),
|
||||
StyleSheetProperty( props[2], value ) };
|
||||
};
|
||||
|
||||
mShorthandParsers["grid"] = []( const ShorthandDefinition* shorthand,
|
||||
std::string value ) -> std::vector<StyleSheetProperty> {
|
||||
value = String::trim( value );
|
||||
if ( value == "none" )
|
||||
return { StyleSheetProperty( "grid-template-rows", "none" ) };
|
||||
return { StyleSheetProperty( "grid-template-rows", value ) };
|
||||
};
|
||||
}
|
||||
|
||||
}}} // namespace EE::UI::CSS
|
||||
|
||||
1605
src/eepp/ui/gridlayouter.cpp
Normal file
1605
src/eepp/ui/gridlayouter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -78,6 +78,7 @@ UIHTMLWidget::~UIHTMLWidget() {
|
||||
mScrollTarget->removeEventListener( mScrollCb );
|
||||
eeSAFE_DELETE( mLayouter );
|
||||
eeSAFE_DELETE( mFlexState );
|
||||
eeSAFE_DELETE( mGridState );
|
||||
}
|
||||
|
||||
UILayouter* UIHTMLWidget::getLayouter() {
|
||||
@@ -114,11 +115,11 @@ void UIHTMLWidget::setDisplay( CSSDisplay display ) {
|
||||
mNodeFlags |= NODE_FLAG_OVER_FIND_ALLOWED;
|
||||
|
||||
if ( mDisplay == CSSDisplay::InlineBlock || mDisplay == CSSDisplay::Inline ||
|
||||
mDisplay == CSSDisplay::InlineFlex ) {
|
||||
mDisplay == CSSDisplay::InlineFlex || mDisplay == CSSDisplay::InlineGrid ) {
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::MatchParent )
|
||||
setLayoutWidthPolicy( SizePolicy::WrapContent );
|
||||
} else if ( mDisplay == CSSDisplay::Block || mDisplay == CSSDisplay::ListItem ||
|
||||
mDisplay == CSSDisplay::Flex ) {
|
||||
mDisplay == CSSDisplay::Flex || mDisplay == CSSDisplay::Grid ) {
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::WrapContent &&
|
||||
mPosition != CSSPosition::Absolute && mPosition != CSSPosition::Fixed )
|
||||
setLayoutWidthPolicy( SizePolicy::MatchParent );
|
||||
@@ -138,6 +139,10 @@ bool UIHTMLWidget::isFlex() const {
|
||||
return mDisplay == CSSDisplay::Flex || mDisplay == CSSDisplay::InlineFlex;
|
||||
}
|
||||
|
||||
bool UIHTMLWidget::isGrid() const {
|
||||
return mDisplay == CSSDisplay::Grid || mDisplay == CSSDisplay::InlineGrid;
|
||||
}
|
||||
|
||||
Float UIHTMLWidget::getBaseline() const {
|
||||
if ( isFlex() && mLayouter ) {
|
||||
auto* flex = reinterpret_cast<FlexLayouter*>( mLayouter );
|
||||
@@ -223,37 +228,38 @@ void UIHTMLWidget::setNeedsOrderSort( bool val ) {
|
||||
}
|
||||
|
||||
void UIHTMLWidget::drawChildren() {
|
||||
bool needsFlexDirectionReverse = false;
|
||||
if ( isFlex() ) {
|
||||
CSSFlexDirection dir = getFlexDirection();
|
||||
bool needsDirectionReverse =
|
||||
needsFlexDirectionReverse =
|
||||
dir == CSSFlexDirection::RowReverse || dir == CSSFlexDirection::ColumnReverse;
|
||||
if ( mNeedsOrderSort || needsDirectionReverse ) {
|
||||
std::vector<Node*> sortedChildren;
|
||||
for ( Node* child = getFirstChild(); child; child = child->getNextNode() )
|
||||
sortedChildren.push_back( child );
|
||||
}
|
||||
|
||||
if ( mNeedsOrderSort ) {
|
||||
std::stable_sort(
|
||||
sortedChildren.begin(), sortedChildren.end(), []( Node* a, Node* b ) {
|
||||
int aOrder = ( a->isWidget() && a->isType( UI_TYPE_HTML_WIDGET ) )
|
||||
? static_cast<UIHTMLWidget*>( a )->getOrder()
|
||||
: 0;
|
||||
int bOrder = ( b->isWidget() && b->isType( UI_TYPE_HTML_WIDGET ) )
|
||||
? static_cast<UIHTMLWidget*>( b )->getOrder()
|
||||
: 0;
|
||||
return aOrder < bOrder;
|
||||
} );
|
||||
}
|
||||
if ( mNeedsOrderSort || needsFlexDirectionReverse ) {
|
||||
std::vector<Node*> sortedChildren;
|
||||
for ( Node* child = getFirstChild(); child; child = child->getNextNode() )
|
||||
sortedChildren.push_back( child );
|
||||
|
||||
if ( needsDirectionReverse )
|
||||
std::reverse( sortedChildren.begin(), sortedChildren.end() );
|
||||
|
||||
for ( auto* child : sortedChildren ) {
|
||||
if ( child->isVisible() )
|
||||
child->nodeDraw();
|
||||
}
|
||||
return;
|
||||
if ( mNeedsOrderSort ) {
|
||||
std::stable_sort( sortedChildren.begin(), sortedChildren.end(), []( Node* a, Node* b ) {
|
||||
int aOrder = ( a->isWidget() && a->isType( UI_TYPE_HTML_WIDGET ) )
|
||||
? static_cast<UIHTMLWidget*>( a )->getOrder()
|
||||
: 0;
|
||||
int bOrder = ( b->isWidget() && b->isType( UI_TYPE_HTML_WIDGET ) )
|
||||
? static_cast<UIHTMLWidget*>( b )->getOrder()
|
||||
: 0;
|
||||
return aOrder < bOrder;
|
||||
} );
|
||||
}
|
||||
|
||||
if ( needsFlexDirectionReverse )
|
||||
std::reverse( sortedChildren.begin(), sortedChildren.end() );
|
||||
|
||||
for ( auto* child : sortedChildren ) {
|
||||
if ( child->isVisible() )
|
||||
child->nodeDraw();
|
||||
}
|
||||
return;
|
||||
}
|
||||
Node::drawChildren();
|
||||
}
|
||||
@@ -354,6 +360,118 @@ void UIHTMLWidget::setColumnGap( const std::string& val ) {
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridTemplateRows( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->templateRows != val ) {
|
||||
gs->templateRows = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridTemplateColumns( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->templateColumns != val ) {
|
||||
gs->templateColumns = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridTemplateAreas( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->templateAreas != val ) {
|
||||
gs->templateAreas = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridAutoRows( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->autoRows != val ) {
|
||||
gs->autoRows = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridAutoColumns( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->autoColumns != val ) {
|
||||
gs->autoColumns = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridAutoFlow( CSSGridAutoFlow val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->autoFlow != val ) {
|
||||
gs->autoFlow = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridAutoFlowDense( bool val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->autoFlowDense != val ) {
|
||||
gs->autoFlowDense = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridRowStart( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->rowStart != val ) {
|
||||
gs->rowStart = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridRowEnd( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->rowEnd != val ) {
|
||||
gs->rowEnd = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridColumnStart( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->columnStart != val ) {
|
||||
gs->columnStart = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridColumnEnd( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->columnEnd != val ) {
|
||||
gs->columnEnd = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setGridArea( const std::string& val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->area != val ) {
|
||||
gs->area = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setJustifyItems( CSSJustifyItems val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->justifyItems != val ) {
|
||||
gs->justifyItems = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLWidget::setJustifySelf( CSSJustifySelf val ) {
|
||||
auto* gs = ensureGridState();
|
||||
if ( gs->justifySelf != val ) {
|
||||
gs->justifySelf = val;
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PropertyId> UIHTMLWidget::getPropertiesImplemented() const {
|
||||
auto props = UILayout::getPropertiesImplemented();
|
||||
auto local = { PropertyId::Display,
|
||||
@@ -380,7 +498,24 @@ std::vector<PropertyId> UIHTMLWidget::getPropertiesImplemented() const {
|
||||
PropertyId::Order,
|
||||
PropertyId::ColumnGap,
|
||||
PropertyId::RowGap,
|
||||
PropertyId::Gap };
|
||||
PropertyId::Gap,
|
||||
PropertyId::GridTemplateRows,
|
||||
PropertyId::GridTemplateColumns,
|
||||
PropertyId::GridTemplateAreas,
|
||||
PropertyId::GridTemplate,
|
||||
PropertyId::GridAutoRows,
|
||||
PropertyId::GridAutoColumns,
|
||||
PropertyId::GridAutoFlow,
|
||||
PropertyId::Grid,
|
||||
PropertyId::GridRowStart,
|
||||
PropertyId::GridRowEnd,
|
||||
PropertyId::GridColumnStart,
|
||||
PropertyId::GridColumnEnd,
|
||||
PropertyId::GridRow,
|
||||
PropertyId::GridColumn,
|
||||
PropertyId::GridArea,
|
||||
PropertyId::JustifyItems,
|
||||
PropertyId::JustifySelf };
|
||||
props.insert( props.end(), local.begin(), local.end() );
|
||||
return props;
|
||||
}
|
||||
@@ -435,6 +570,32 @@ std::string UIHTMLWidget::getPropertyString( const PropertyDefinition* propertyD
|
||||
return getRowGap();
|
||||
case PropertyId::ColumnGap:
|
||||
return getColumnGap();
|
||||
case PropertyId::GridTemplateRows:
|
||||
return getGridTemplateRows();
|
||||
case PropertyId::GridTemplateColumns:
|
||||
return getGridTemplateColumns();
|
||||
case PropertyId::GridTemplateAreas:
|
||||
return getGridTemplateAreas();
|
||||
case PropertyId::GridAutoRows:
|
||||
return getGridAutoRows();
|
||||
case PropertyId::GridAutoColumns:
|
||||
return getGridAutoColumns();
|
||||
case PropertyId::GridAutoFlow:
|
||||
return CSSGridAutoFlowHelper::toString( getGridAutoFlow() );
|
||||
case PropertyId::GridRowStart:
|
||||
return getGridRowStart();
|
||||
case PropertyId::GridRowEnd:
|
||||
return getGridRowEnd();
|
||||
case PropertyId::GridColumnStart:
|
||||
return getGridColumnStart();
|
||||
case PropertyId::GridColumnEnd:
|
||||
return getGridColumnEnd();
|
||||
case PropertyId::GridArea:
|
||||
return getGridArea();
|
||||
case PropertyId::JustifyItems:
|
||||
return CSSJustifyItemsHelper::toString( getJustifyItems() );
|
||||
case PropertyId::JustifySelf:
|
||||
return CSSJustifySelfHelper::toString( getJustifySelf() );
|
||||
default:
|
||||
return UILayout::getPropertyString( propertyDef );
|
||||
}
|
||||
@@ -547,6 +708,64 @@ bool UIHTMLWidget::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
setColumnGap( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridTemplateRows: {
|
||||
setGridTemplateRows( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridTemplateColumns: {
|
||||
setGridTemplateColumns( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridTemplateAreas: {
|
||||
setGridTemplateAreas( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridAutoRows: {
|
||||
setGridAutoRows( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridAutoColumns: {
|
||||
setGridAutoColumns( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridAutoFlow: {
|
||||
std::string val = attribute.asString();
|
||||
String::toLowerInPlace( val );
|
||||
setGridAutoFlowDense( val.find( "dense" ) != std::string::npos );
|
||||
if ( val.find( "column" ) != std::string::npos )
|
||||
setGridAutoFlow( CSSGridAutoFlow::Column );
|
||||
else
|
||||
setGridAutoFlow( CSSGridAutoFlow::Row );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridRowStart: {
|
||||
setGridRowStart( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridRowEnd: {
|
||||
setGridRowEnd( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridColumnStart: {
|
||||
setGridColumnStart( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridColumnEnd: {
|
||||
setGridColumnEnd( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::GridArea: {
|
||||
setGridArea( attribute.asString() );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::JustifyItems: {
|
||||
setJustifyItems( CSSJustifyItemsHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
case PropertyId::JustifySelf: {
|
||||
setJustifySelf( CSSJustifySelfHelper::fromString( attribute.asString() ) );
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <eepp/core/memorymanager.hpp>
|
||||
#include <eepp/ui/blocklayouter.hpp>
|
||||
#include <eepp/ui/flexlayouter.hpp>
|
||||
#include <eepp/ui/gridlayouter.hpp>
|
||||
#include <eepp/ui/inlinelayouter.hpp>
|
||||
#include <eepp/ui/nonelayouter.hpp>
|
||||
#include <eepp/ui/tablelayouter.hpp>
|
||||
@@ -16,12 +17,22 @@ static bool parentIsFlexContainer( UIWidget* widget ) {
|
||||
parent->asType<UIHTMLWidget>()->isFlex();
|
||||
}
|
||||
|
||||
static bool parentIsGridContainer( UIWidget* widget ) {
|
||||
Node* parent = widget->getParent();
|
||||
return parent && parent->isType( UI_TYPE_HTML_WIDGET ) &&
|
||||
parent->asType<UIHTMLWidget>()->isGrid();
|
||||
}
|
||||
|
||||
UILayouter* UILayouterManager::create( CSSDisplay display, UIWidget* container ) {
|
||||
// Blockification per CSS Flexbox §4: children of flex containers are block-level
|
||||
if ( parentIsFlexContainer( container ) ) {
|
||||
// But a child that is itself a flex container still needs FlexLayouter
|
||||
// Blockification per CSS Flexbox §4 and CSS Grid §6: children of flex / grid containers
|
||||
// are block-level
|
||||
if ( parentIsFlexContainer( container ) || parentIsGridContainer( container ) ) {
|
||||
// But a child that is itself a flex container still needs FlexLayouter,
|
||||
// and a child that is itself a grid container still needs GridLayouter.
|
||||
if ( display == CSSDisplay::Flex || display == CSSDisplay::InlineFlex )
|
||||
return eeNew( FlexLayouter, ( container ) );
|
||||
if ( display == CSSDisplay::Grid || display == CSSDisplay::InlineGrid )
|
||||
return eeNew( GridLayouter, ( container ) );
|
||||
return eeNew( BlockLayouter, ( container ) );
|
||||
}
|
||||
|
||||
@@ -34,6 +45,9 @@ UILayouter* UILayouterManager::create( CSSDisplay display, UIWidget* container )
|
||||
case CSSDisplay::Flex:
|
||||
case CSSDisplay::InlineFlex:
|
||||
return eeNew( FlexLayouter, ( container ) );
|
||||
case CSSDisplay::Grid:
|
||||
case CSSDisplay::InlineGrid:
|
||||
return eeNew( GridLayouter, ( container ) );
|
||||
case CSSDisplay::Inline:
|
||||
if ( container->isType( UI_TYPE_TEXTSPAN ) )
|
||||
return eeNew( InlineLayouter, ( container ) );
|
||||
|
||||
@@ -1197,7 +1197,10 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
|
||||
Node* parentNode = container->getParent();
|
||||
bool parentIsFlex = parentNode->isType( UI_TYPE_HTML_WIDGET ) &&
|
||||
parentNode->asType<UIHTMLWidget>()->isFlex();
|
||||
if ( !selfSpan->getText().empty() && ( !selfSpan->isInline() || parentIsFlex ) &&
|
||||
bool parentIsGrid = parentNode->isType( UI_TYPE_HTML_WIDGET ) &&
|
||||
parentNode->asType<UIHTMLWidget>()->isGrid();
|
||||
if ( !selfSpan->getText().empty() &&
|
||||
( !selfSpan->isInline() || parentIsFlex || parentIsGrid ) &&
|
||||
NULL != selfSpan->getFontStyleConfig().Font ) {
|
||||
String::View selfText = selfSpan->getText().view();
|
||||
String transformed;
|
||||
|
||||
1718
src/tests/unit_tests/uihtml_grid_test.cpp
Normal file
1718
src/tests/unit_tests/uihtml_grid_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -178,6 +178,7 @@ UTEST( UIHTML, redditOldThreadWebViewSmoke ) {
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
|
||||
if ( !FileSystem::fileExists( "assets/html/reddit_old_thread_files/reddit.ETA_etA2z5U.css" ) ) {
|
||||
|
||||
Engine::destroySingleton();
|
||||
UTEST_SKIP( "old Reddit fixture CSS asset is not readable" );
|
||||
}
|
||||
@@ -348,8 +349,8 @@ UTEST( UIHTML, redditOldThreadWebViewSmoke ) {
|
||||
<< ") "
|
||||
<< "header=(" << headerPos.x << "," << headerPos.y << " "
|
||||
<< header->asType<UIWidget>()->getPixelsSize().getWidth() << "x"
|
||||
<< header->asType<UIWidget>()->getPixelsSize().getHeight() << " offset="
|
||||
<< header->asType<UIWidget>()->getPixelsContentOffset().Left << ","
|
||||
<< header->asType<UIWidget>()->getPixelsSize().getHeight()
|
||||
<< " offset=" << header->asType<UIWidget>()->getPixelsContentOffset().Left << ","
|
||||
<< header->asType<UIWidget>()->getPixelsContentOffset().Top << ","
|
||||
<< header->asType<UIWidget>()->getPixelsContentOffset().Right << ","
|
||||
<< header->asType<UIWidget>()->getPixelsContentOffset().Bottom << ") "
|
||||
@@ -2589,8 +2590,7 @@ UTEST( UIHTML, BodyHeightMiscalculationFixture ) {
|
||||
|
||||
// With justify-content: space-between in a row-direction flex:
|
||||
// .brand should be at the left side of .wrap (near padding edge).
|
||||
EXPECT_LT( brandWidget->getPixelsPosition().x,
|
||||
wrapWidget->getPixelsSize().getWidth() * 0.25f );
|
||||
EXPECT_LT( brandWidget->getPixelsPosition().x, wrapWidget->getPixelsSize().getWidth() * 0.25f );
|
||||
|
||||
// .links should be at the right side of .wrap, NOT next to .brand.
|
||||
Float wrapW = wrapWidget->getPixelsSize().getWidth();
|
||||
|
||||
Reference in New Issue
Block a user