mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-11 17:17:42 +03:00
Initial support for HTML element tags: details and summary.
This commit is contained in:
158
bin/unit_tests/assets/html/lobsters_item.html
Normal file
158
bin/unit_tests/assets/html/lobsters_item.html
Normal file
@@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang=en>
|
||||
<title>Lobsters</title>
|
||||
<style>
|
||||
:root {
|
||||
--font-size-default: 1rem;
|
||||
--font-size-tags: 0.66rem;
|
||||
--base-light: 255 255 255;
|
||||
--base-shadow: 0 0 0;
|
||||
--comment-controls-height: 1.2lh;
|
||||
--input-outline-width: 2px
|
||||
--color-fg: black;
|
||||
--color-fg-link: black;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--font-size-default);
|
||||
}
|
||||
|
||||
body > * {
|
||||
font-family: arial, sans-serif;
|
||||
color: var(--color-fg);
|
||||
line-height: 1.45em
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 auto;
|
||||
padding-bottom: 2em
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-fg-link);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
ol.stories {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
li.story {
|
||||
clear: both
|
||||
}
|
||||
|
||||
li div.details {
|
||||
padding-top: 0.1em
|
||||
}
|
||||
|
||||
li .link {
|
||||
font-weight: bold;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
li .link a {
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
li.story .description_present {
|
||||
padding-left: 0.25em;
|
||||
text-decoration: none;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
li.story a.tag {
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
li .tags {
|
||||
margin-right: 0.25em
|
||||
}
|
||||
|
||||
li .domain {
|
||||
font-style: italic;
|
||||
text-decoration: none;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.caches {
|
||||
display: inline-block;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.caches summary {
|
||||
list-style: none
|
||||
}
|
||||
|
||||
.caches ul {
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-box-border);
|
||||
white-space: nowrap;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
.caches li {
|
||||
border-bottom: 1px solid var(--color-box-border)
|
||||
}
|
||||
|
||||
.caches li:last-child {
|
||||
border-bottom: 0
|
||||
}
|
||||
|
||||
.caches a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 3px 7px
|
||||
}
|
||||
|
||||
.caches a:hover {
|
||||
text-decoration: underline
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-username data-now-unix=1778970488>
|
||||
<div id=inside>
|
||||
<ol class="stories list">
|
||||
<li id=story_4g74mw data-shortid=4g74mw class=story>
|
||||
<div class="story_liner h-entry">
|
||||
<div class=details>
|
||||
<div class=byline>
|
||||
<span> via </span>
|
||||
<a class="u-author h-card" href=https://lobste.rs/~jbauer>jbauer</a>
|
||||
<time title="2026-05-16 04:45:51" datetime="2026-05-16 04:45:51" data-at-unix=1778924751>12
|
||||
hours ago</time>
|
||||
<span aria-hidden=true> | </span>
|
||||
<details class=caches name=caches>
|
||||
<summary>caches</summary>
|
||||
<ul>
|
||||
<li><a
|
||||
href=https://web.archive.org/web/3/https%3A%2F%2Fratfactor.com%2Fascetic-computing>Archive.org</a>
|
||||
</li>
|
||||
<li><a
|
||||
href="https://ghostarchive.org/search?term=https%3A%2F%2Fratfactor.com%2Fascetic-computing">Ghostarchive</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<span class=comments_label>
|
||||
<span aria-hidden=true> | </span>
|
||||
<a role=heading aria-level=2 href=https://lobste.rs/s/4g74mw/ascetic_computing>
|
||||
10 comments
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href=https://lobste.rs/s/4g74mw/ascetic_computing class=mobile_comments style=display:none>
|
||||
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
@@ -127,6 +127,7 @@ class EE_API Event {
|
||||
OnNavigationCompleted,
|
||||
OnNavigationError,
|
||||
OnTitleChanged,
|
||||
OnToggle,
|
||||
NoEvent = eeINDEX_NOT_FOUND
|
||||
};
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
#include <eepp/ui/uifontstyleconfig.hpp>
|
||||
#include <eepp/ui/uigridlayout.hpp>
|
||||
#include <eepp/ui/uihelper.hpp>
|
||||
#include <eepp/ui/uihtmldetails.hpp>
|
||||
#include <eepp/ui/uihtmlform.hpp>
|
||||
#include <eepp/ui/uihtmlimage.hpp>
|
||||
#include <eepp/ui/uihtmlinput.hpp>
|
||||
|
||||
@@ -251,6 +251,7 @@ enum class PropertyId : Uint32 {
|
||||
Cols = String::hash( "cols" ),
|
||||
InputMode = String::hash( "input-mode" ),
|
||||
Hidden = String::hash( "hidden" ),
|
||||
Open = String::hash( "open" ),
|
||||
Display = String::hash( "display" ),
|
||||
Position = String::hash( "position" ),
|
||||
Top = String::hash( "top" ),
|
||||
|
||||
@@ -44,7 +44,9 @@ enum class CSSListStyleType {
|
||||
LowerAlpha,
|
||||
UpperAlpha,
|
||||
LowerRoman,
|
||||
UpperRoman
|
||||
UpperRoman,
|
||||
DisclosureClosed,
|
||||
DisclosureOpen
|
||||
};
|
||||
|
||||
struct EE_API CSSListStyleTypeHelper {
|
||||
|
||||
@@ -132,6 +132,8 @@ enum UINodeType {
|
||||
UI_TYPE_HTML_LIST_ITEM,
|
||||
UI_TYPE_HTML_IMAGE,
|
||||
UI_TYPE_HTML_FORM,
|
||||
UI_TYPE_HTML_DETAILS,
|
||||
UI_TYPE_HTML_SUMMARY,
|
||||
UI_TYPE_WEBVIEW,
|
||||
UI_TYPE_SVG,
|
||||
UI_TYPE_TEXTNODE,
|
||||
|
||||
110
include/eepp/ui/uihtmldetails.hpp
Normal file
110
include/eepp/ui/uihtmldetails.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#ifndef EE_UI_UIHTMLDETAILS_HPP
|
||||
#define EE_UI_UIHTMLDETAILS_HPP
|
||||
|
||||
#include <eepp/graphics/text.hpp>
|
||||
#include <eepp/ui/uirichtext.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class UIHTMLSummary;
|
||||
|
||||
class EE_API UIHTMLDetails : public UIRichText {
|
||||
public:
|
||||
static UIHTMLDetails* New();
|
||||
|
||||
virtual Uint32 getType() const override;
|
||||
|
||||
virtual bool isType( const Uint32& type ) const override;
|
||||
|
||||
virtual void loadFromXmlNode( const pugi::xml_node& node ) override;
|
||||
|
||||
virtual bool applyProperty( const StyleSheetProperty& attribute ) override;
|
||||
|
||||
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
|
||||
const Uint32& propertyIndex = 0 ) const override;
|
||||
|
||||
virtual std::vector<PropertyId> getPropertiesImplemented() const override;
|
||||
|
||||
virtual void updateLayout() override;
|
||||
|
||||
bool isOpen() const;
|
||||
|
||||
void setOpen( bool open );
|
||||
|
||||
UIHTMLSummary* findSummaryChild() const;
|
||||
|
||||
bool isActiveSummary( const UIHTMLSummary* summary ) const;
|
||||
|
||||
void markSummaryMarkersDirty();
|
||||
|
||||
protected:
|
||||
bool mOpen{ false };
|
||||
UIHTMLSummary* mAutoSummary{ nullptr };
|
||||
UnorderedMap<Node*, bool> mForcedHiddenChildren;
|
||||
bool mSyncingDetailsChildren{ false };
|
||||
|
||||
UIHTMLDetails();
|
||||
|
||||
void ensureAutoSummary();
|
||||
|
||||
void syncDetailsChildrenVisibility();
|
||||
|
||||
virtual void onChildCountChange( Node* child, const bool& removed ) override;
|
||||
};
|
||||
|
||||
class EE_API UIHTMLSummary : public UIRichText {
|
||||
public:
|
||||
static UIHTMLSummary* New();
|
||||
|
||||
virtual Uint32 getType() const override;
|
||||
|
||||
virtual bool isType( const Uint32& type ) const override;
|
||||
|
||||
virtual void draw() override;
|
||||
|
||||
virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags ) override;
|
||||
|
||||
virtual Uint32 onKeyDown( const KeyEvent& event ) override;
|
||||
|
||||
void setDisclosureMarkerDirty();
|
||||
|
||||
CSSListStyleType getListStyleType() const { return mListStyleType; }
|
||||
|
||||
void setListStyleType( CSSListStyleType type );
|
||||
|
||||
virtual bool applyProperty( const StyleSheetProperty& attribute ) override;
|
||||
|
||||
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
|
||||
const Uint32& propertyIndex = 0 ) const override;
|
||||
|
||||
virtual std::vector<PropertyId> getPropertiesImplemented() const override;
|
||||
|
||||
UIHTMLDetails* findParentDetails() const;
|
||||
|
||||
bool toggleParentDetails();
|
||||
|
||||
protected:
|
||||
CSSListStyleType mListStyleType{ CSSListStyleType::DisclosureClosed };
|
||||
std::unique_ptr<Graphics::Text> mListMarkerText;
|
||||
bool mUsingDefaultListStylePadding{ true };
|
||||
bool mApplyingDefaultListStylePadding{ false };
|
||||
|
||||
UIHTMLSummary();
|
||||
|
||||
CSSListStyleType getDisclosureMarkerType() const;
|
||||
|
||||
int countPrecedingSummarySiblings() const;
|
||||
|
||||
void invalidateListMarker();
|
||||
|
||||
void syncDefaultListStylePadding();
|
||||
|
||||
virtual void onFontChanged() override;
|
||||
|
||||
virtual void onFontStyleChanged() override;
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
26
include/eepp/ui/uihtmlliststyle.hpp
Normal file
26
include/eepp/ui/uihtmlliststyle.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef EE_UI_UIHTMLLISTSTYLE_HPP
|
||||
#define EE_UI_UIHTMLLISTSTYLE_HPP
|
||||
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/graphics/fontstyleconfig.hpp>
|
||||
#include <eepp/math/rect.hpp>
|
||||
#include <eepp/ui/csslayouttypes.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
class EE_API UIHTMLListStyle {
|
||||
public:
|
||||
static bool isPrimitiveMarker( CSSListStyleType type );
|
||||
|
||||
static bool isTextMarker( CSSListStyleType type );
|
||||
|
||||
static String getTextMarkerString( CSSListStyleType type, int index );
|
||||
|
||||
static void drawPrimitiveMarker( CSSListStyleType type, const Vector2f& screenPos,
|
||||
const Rectf& paddingPx,
|
||||
const Graphics::FontStyleConfig& style );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
@@ -107,6 +107,10 @@ std::string CSSListStyleTypeHelper::toString( CSSListStyleType type ) {
|
||||
return "lower-roman";
|
||||
case CSSListStyleType::UpperRoman:
|
||||
return "upper-roman";
|
||||
case CSSListStyleType::DisclosureClosed:
|
||||
return "disclosure-closed";
|
||||
case CSSListStyleType::DisclosureOpen:
|
||||
return "disclosure-open";
|
||||
case CSSListStyleType::None:
|
||||
default:
|
||||
return "none";
|
||||
@@ -130,6 +134,10 @@ CSSListStyleType CSSListStyleTypeHelper::fromString( std::string_view val ) {
|
||||
return CSSListStyleType::LowerRoman;
|
||||
if ( val == "upper-roman" )
|
||||
return CSSListStyleType::UpperRoman;
|
||||
if ( val == "disclosure-closed" )
|
||||
return CSSListStyleType::DisclosureClosed;
|
||||
if ( val == "disclosure-open" )
|
||||
return CSSListStyleType::DisclosureOpen;
|
||||
return CSSListStyleType::None;
|
||||
}
|
||||
|
||||
|
||||
@@ -445,6 +445,7 @@ void StyleSheetSpecification::registerDefaultProperties() {
|
||||
registerProperty( "input-mode", "normal" ).setType( PropertyType::String );
|
||||
|
||||
registerProperty( "hidden", "" ).setType( PropertyType::Bool );
|
||||
registerProperty( "open", "" ).setType( PropertyType::Bool );
|
||||
registerProperty( "display", "inline" ).setType( PropertyType::String );
|
||||
registerProperty( "position", "static" ).setType( PropertyType::String );
|
||||
registerProperty( "float", "none" ).setType( PropertyType::String );
|
||||
|
||||
381
src/eepp/ui/uihtmldetails.cpp
Normal file
381
src/eepp/ui/uihtmldetails.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
#include <eepp/scene/keyevent.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/uihtmldetails.hpp>
|
||||
#include <eepp/ui/uihtmlliststyle.hpp>
|
||||
#include <eepp/ui/uistyle.hpp>
|
||||
#include <eepp/ui/uitextnode.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
static bool htmlBoolAttributeIsTrue( const StyleSheetProperty& property ) {
|
||||
if ( property.value().empty() )
|
||||
return true;
|
||||
|
||||
std::string value( property.value() );
|
||||
String::toLowerInPlace( value );
|
||||
if ( value == property.getName() )
|
||||
return true;
|
||||
if ( value == "false" || value == "0" || value == "no" )
|
||||
return false;
|
||||
return property.asBool();
|
||||
}
|
||||
|
||||
UIHTMLDetails* UIHTMLDetails::New() {
|
||||
return eeNew( UIHTMLDetails, () );
|
||||
}
|
||||
|
||||
UIHTMLDetails::UIHTMLDetails() : UIRichText( "details" ) {}
|
||||
|
||||
Uint32 UIHTMLDetails::getType() const {
|
||||
return UI_TYPE_HTML_DETAILS;
|
||||
}
|
||||
|
||||
bool UIHTMLDetails::isType( const Uint32& type ) const {
|
||||
return UIHTMLDetails::getType() == type ? true : UIRichText::isType( type );
|
||||
}
|
||||
|
||||
void UIHTMLDetails::loadFromXmlNode( const pugi::xml_node& node ) {
|
||||
UIRichText::loadFromXmlNode( node );
|
||||
syncDetailsChildrenVisibility();
|
||||
}
|
||||
|
||||
bool UIHTMLDetails::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
if ( !checkPropertyDefinition( attribute ) )
|
||||
return false;
|
||||
|
||||
if ( attribute.getPropertyDefinition()->getPropertyId() == PropertyId::Open ) {
|
||||
setOpen( htmlBoolAttributeIsTrue( attribute ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
return UIRichText::applyProperty( attribute );
|
||||
}
|
||||
|
||||
std::string UIHTMLDetails::getPropertyString( const PropertyDefinition* propertyDef,
|
||||
const Uint32& propertyIndex ) const {
|
||||
if ( NULL == propertyDef )
|
||||
return "";
|
||||
|
||||
switch ( propertyDef->getPropertyId() ) {
|
||||
case PropertyId::Open:
|
||||
return mOpen ? "true" : "false";
|
||||
default:
|
||||
return UIRichText::getPropertyString( propertyDef, propertyIndex );
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PropertyId> UIHTMLDetails::getPropertiesImplemented() const {
|
||||
auto props = UIRichText::getPropertiesImplemented();
|
||||
props.push_back( PropertyId::Open );
|
||||
return props;
|
||||
}
|
||||
|
||||
void UIHTMLDetails::updateLayout() {
|
||||
syncDetailsChildrenVisibility();
|
||||
UIRichText::updateLayout();
|
||||
}
|
||||
|
||||
bool UIHTMLDetails::isOpen() const {
|
||||
return mOpen;
|
||||
}
|
||||
|
||||
void UIHTMLDetails::setOpen( bool open ) {
|
||||
if ( mOpen == open )
|
||||
return;
|
||||
|
||||
mOpen = open;
|
||||
syncDetailsChildrenVisibility();
|
||||
markSummaryMarkersDirty();
|
||||
sendCommonEvent( Event::OnToggle );
|
||||
setLayoutDirty();
|
||||
invalidateDraw();
|
||||
}
|
||||
|
||||
UIHTMLSummary* UIHTMLDetails::findSummaryChild() const {
|
||||
Node* child = getFirstChild();
|
||||
while ( child ) {
|
||||
if ( child->isType( UI_TYPE_HTML_SUMMARY ) ) {
|
||||
auto* summary = child->asType<UIHTMLSummary>();
|
||||
if ( summary != mAutoSummary )
|
||||
return summary;
|
||||
}
|
||||
child = child->getNextNode();
|
||||
}
|
||||
|
||||
return mAutoSummary && mAutoSummary->getParent() == this ? mAutoSummary : nullptr;
|
||||
}
|
||||
|
||||
bool UIHTMLDetails::isActiveSummary( const UIHTMLSummary* summary ) const {
|
||||
return summary != nullptr && findSummaryChild() == summary;
|
||||
}
|
||||
|
||||
void UIHTMLDetails::markSummaryMarkersDirty() {
|
||||
Node* child = getFirstChild();
|
||||
while ( child ) {
|
||||
if ( child->isType( UI_TYPE_HTML_SUMMARY ) )
|
||||
child->asType<UIHTMLSummary>()->setDisclosureMarkerDirty();
|
||||
child = child->getNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLDetails::ensureAutoSummary() {
|
||||
Node* child = getFirstChild();
|
||||
while ( child ) {
|
||||
if ( child->isType( UI_TYPE_HTML_SUMMARY ) && child != mAutoSummary )
|
||||
return;
|
||||
child = child->getNextNode();
|
||||
}
|
||||
|
||||
if ( mAutoSummary && mAutoSummary->getParent() == this )
|
||||
return;
|
||||
|
||||
mAutoSummary = UIHTMLSummary::New();
|
||||
mAutoSummary->setParent( this );
|
||||
mAutoSummary->toBack();
|
||||
|
||||
auto* text = UITextNode::New();
|
||||
text->setText( "Details" );
|
||||
text->setParent( mAutoSummary );
|
||||
}
|
||||
|
||||
void UIHTMLDetails::syncDetailsChildrenVisibility() {
|
||||
if ( mSyncingDetailsChildren )
|
||||
return;
|
||||
|
||||
mSyncingDetailsChildren = true;
|
||||
ensureAutoSummary();
|
||||
UIHTMLSummary* activeSummary = findSummaryChild();
|
||||
|
||||
for ( auto it = mForcedHiddenChildren.begin(); it != mForcedHiddenChildren.end(); ) {
|
||||
if ( it->first->getParent() != this )
|
||||
it = mForcedHiddenChildren.erase( it );
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
Node* child = getFirstChild();
|
||||
while ( child ) {
|
||||
Node* next = child->getNextNode();
|
||||
if ( child == mAutoSummary && activeSummary != mAutoSummary ) {
|
||||
mForcedHiddenChildren.erase( child );
|
||||
child->setVisible( false );
|
||||
} else if ( child == activeSummary ) {
|
||||
auto forcedIt = mForcedHiddenChildren.find( child );
|
||||
if ( forcedIt != mForcedHiddenChildren.end() ) {
|
||||
child->setVisible( forcedIt->second );
|
||||
mForcedHiddenChildren.erase( forcedIt );
|
||||
} else if ( !child->isVisible() ) {
|
||||
child->setVisible( true );
|
||||
}
|
||||
} else if ( mOpen ) {
|
||||
auto forcedIt = mForcedHiddenChildren.find( child );
|
||||
if ( forcedIt != mForcedHiddenChildren.end() ) {
|
||||
child->setVisible( forcedIt->second );
|
||||
mForcedHiddenChildren.erase( forcedIt );
|
||||
}
|
||||
} else {
|
||||
if ( mForcedHiddenChildren.find( child ) == mForcedHiddenChildren.end() )
|
||||
mForcedHiddenChildren[child] = child->isVisible();
|
||||
child->setVisible( false );
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
mSyncingDetailsChildren = false;
|
||||
}
|
||||
|
||||
void UIHTMLDetails::onChildCountChange( Node* child, const bool& removed ) {
|
||||
if ( removed ) {
|
||||
mForcedHiddenChildren.erase( child );
|
||||
if ( child == mAutoSummary && !mSyncingDetailsChildren )
|
||||
mAutoSummary = nullptr;
|
||||
}
|
||||
|
||||
UIRichText::onChildCountChange( child, removed );
|
||||
if ( !mSyncingDetailsChildren )
|
||||
syncDetailsChildrenVisibility();
|
||||
}
|
||||
|
||||
UIHTMLSummary* UIHTMLSummary::New() {
|
||||
return eeNew( UIHTMLSummary, () );
|
||||
}
|
||||
|
||||
UIHTMLSummary::UIHTMLSummary() : UIRichText( "summary" ) {
|
||||
mDisplay = CSSDisplay::ListItem;
|
||||
setFlags( UI_CREATING_NODE );
|
||||
applyProperty( StyleSheetProperty( "cursor", "pointer" ) );
|
||||
applyProperty( StyleSheetProperty( "padding-left", "20dp" ) );
|
||||
applyProperty( StyleSheetProperty( "list-style-type", "disclosure-closed" ) );
|
||||
getUIStyle()->setStyleSheetProperty( StyleSheetProperty( "cursor", "pointer" ) );
|
||||
getUIStyle()->setStyleSheetProperty(
|
||||
StyleSheetProperty( "list-style-type", "disclosure-closed" ) );
|
||||
unsetFlags( UI_CREATING_NODE );
|
||||
setFlags( UI_TAB_FOCUSABLE );
|
||||
}
|
||||
|
||||
Uint32 UIHTMLSummary::getType() const {
|
||||
return UI_TYPE_HTML_SUMMARY;
|
||||
}
|
||||
|
||||
bool UIHTMLSummary::isType( const Uint32& type ) const {
|
||||
return UIHTMLSummary::getType() == type ? true : UIRichText::isType( type );
|
||||
}
|
||||
|
||||
void UIHTMLSummary::draw() {
|
||||
UIRichText::draw();
|
||||
if ( mVisible && 0.f != mAlpha ) {
|
||||
const CSSListStyleType markerType = getDisclosureMarkerType();
|
||||
if ( UIHTMLListStyle::isPrimitiveMarker( markerType ) ) {
|
||||
UIHTMLListStyle::drawPrimitiveMarker( markerType, mScreenPos, mPaddingPx,
|
||||
mRichText.getFontStyleConfig() );
|
||||
} else if ( mListMarkerText && !mListMarkerText->getString().empty() ) {
|
||||
const Float fontSize = mRichText.getFontStyleConfig().CharacterSize;
|
||||
const Float offset = 0.25f * fontSize;
|
||||
const Float markerX =
|
||||
mScreenPos.x + mPaddingPx.Left - mListMarkerText->getTextWidth() - offset;
|
||||
mListMarkerText->draw( markerX, mScreenPos.y + mPaddingPx.Top, Vector2f::One, 0.f,
|
||||
getBlendMode() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLSummary::setDisclosureMarkerDirty() {
|
||||
invalidateDraw();
|
||||
}
|
||||
|
||||
UIHTMLDetails* UIHTMLSummary::findParentDetails() const {
|
||||
Node* parent = getParent();
|
||||
while ( parent ) {
|
||||
if ( parent->isType( UI_TYPE_HTML_DETAILS ) )
|
||||
return parent->asType<UIHTMLDetails>();
|
||||
parent = parent->getParent();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UIHTMLSummary::toggleParentDetails() {
|
||||
UIHTMLDetails* details = findParentDetails();
|
||||
if ( details && details->isActiveSummary( this ) ) {
|
||||
details->setOpen( !details->isOpen() );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UIHTMLSummary::setListStyleType( CSSListStyleType type ) {
|
||||
if ( mListStyleType != type ) {
|
||||
mListStyleType = type;
|
||||
syncDefaultListStylePadding();
|
||||
invalidateListMarker();
|
||||
}
|
||||
}
|
||||
|
||||
bool UIHTMLSummary::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
if ( !checkPropertyDefinition( attribute ) )
|
||||
return false;
|
||||
|
||||
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
|
||||
case PropertyId::ListStyleType:
|
||||
setListStyleType( CSSListStyleTypeHelper::fromString( attribute.value() ) );
|
||||
return true;
|
||||
case PropertyId::PaddingLeft:
|
||||
if ( !isCreatingNode() && !mApplyingDefaultListStylePadding )
|
||||
mUsingDefaultListStylePadding = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return UIRichText::applyProperty( attribute );
|
||||
}
|
||||
|
||||
std::string UIHTMLSummary::getPropertyString( const PropertyDefinition* propertyDef,
|
||||
const Uint32& propertyIndex ) const {
|
||||
if ( NULL == propertyDef )
|
||||
return "";
|
||||
|
||||
switch ( propertyDef->getPropertyId() ) {
|
||||
case PropertyId::ListStyleType:
|
||||
return CSSListStyleTypeHelper::toString( mListStyleType );
|
||||
default:
|
||||
return UIRichText::getPropertyString( propertyDef, propertyIndex );
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PropertyId> UIHTMLSummary::getPropertiesImplemented() const {
|
||||
auto props = UIRichText::getPropertiesImplemented();
|
||||
props.push_back( PropertyId::ListStyleType );
|
||||
return props;
|
||||
}
|
||||
|
||||
CSSListStyleType UIHTMLSummary::getDisclosureMarkerType() const {
|
||||
UIHTMLDetails* details = findParentDetails();
|
||||
if ( mListStyleType == CSSListStyleType::DisclosureClosed && details && details->isOpen() )
|
||||
return CSSListStyleType::DisclosureOpen;
|
||||
return mListStyleType;
|
||||
}
|
||||
|
||||
int UIHTMLSummary::countPrecedingSummarySiblings() const {
|
||||
int count = 0;
|
||||
Node* prev = getPrevNode();
|
||||
while ( prev ) {
|
||||
if ( prev->isWidget() && prev->asType<UIWidget>()->getElementTag() == "summary" )
|
||||
count++;
|
||||
prev = prev->getPrevNode();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void UIHTMLSummary::invalidateListMarker() {
|
||||
if ( !UIHTMLListStyle::isTextMarker( mListStyleType ) ) {
|
||||
mListMarkerText.reset();
|
||||
} else {
|
||||
String markerStr =
|
||||
UIHTMLListStyle::getTextMarkerString( mListStyleType, countPrecedingSummarySiblings() );
|
||||
if ( !markerStr.empty() ) {
|
||||
if ( !mListMarkerText )
|
||||
mListMarkerText = std::make_unique<Text>();
|
||||
mListMarkerText->setString( markerStr );
|
||||
mListMarkerText->setStyleConfig( mRichText.getFontStyleConfig() );
|
||||
} else {
|
||||
mListMarkerText.reset();
|
||||
}
|
||||
}
|
||||
invalidateDraw();
|
||||
}
|
||||
|
||||
void UIHTMLSummary::syncDefaultListStylePadding() {
|
||||
if ( !mUsingDefaultListStylePadding )
|
||||
return;
|
||||
|
||||
mApplyingDefaultListStylePadding = true;
|
||||
UIRichText::applyProperty( StyleSheetProperty(
|
||||
"padding-left", mListStyleType == CSSListStyleType::None ? "0dp" : "20dp" ) );
|
||||
mApplyingDefaultListStylePadding = false;
|
||||
}
|
||||
|
||||
Uint32 UIHTMLSummary::onMouseClick( const Vector2i& position, const Uint32& flags ) {
|
||||
if ( toggleParentDetails() )
|
||||
return 1;
|
||||
return UIRichText::onMouseClick( position, flags );
|
||||
}
|
||||
|
||||
Uint32 UIHTMLSummary::onKeyDown( const KeyEvent& event ) {
|
||||
if ( event.getKeyCode() == KEY_RETURN || event.getKeyCode() == KEY_KP_ENTER ||
|
||||
event.getKeyCode() == KEY_SPACE ) {
|
||||
if ( toggleParentDetails() )
|
||||
return 1;
|
||||
}
|
||||
return UIRichText::onKeyDown( event );
|
||||
}
|
||||
|
||||
void UIHTMLSummary::onFontChanged() {
|
||||
UIRichText::onFontChanged();
|
||||
invalidateListMarker();
|
||||
}
|
||||
|
||||
void UIHTMLSummary::onFontStyleChanged() {
|
||||
UIRichText::onFontStyleChanged();
|
||||
invalidateListMarker();
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/uihtmllistitem.hpp>
|
||||
#include <eepp/ui/uihtmlliststyle.hpp>
|
||||
#include <eepp/ui/uilayouter.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
@@ -41,36 +41,10 @@ void UIHTMLListItem::draw() {
|
||||
const FontStyleConfig& style = mRichText.getFontStyleConfig();
|
||||
Float fontSize = style.CharacterSize;
|
||||
Float offset = 0.25f * fontSize;
|
||||
Float lineHeight =
|
||||
style.Font ? style.Font->getLineSpacing( (unsigned int)fontSize ) : fontSize;
|
||||
Float lineTop = mScreenPos.y + mPaddingPx.Top;
|
||||
|
||||
if ( mListStyleType == CSSListStyleType::Disc ) {
|
||||
Float radius = fontSize * 0.22f;
|
||||
Float markerX = std::floor( mScreenPos.x + mPaddingPx.Left - radius * 2.f - offset );
|
||||
Float markerY = std::floor( lineTop + ( lineHeight - radius * 2.f ) * 0.5f + radius );
|
||||
Primitives p;
|
||||
p.setColor( style.FontColor );
|
||||
p.setFillMode( PrimitiveFillMode::DRAW_FILL );
|
||||
p.drawCircle( { markerX, markerY }, radius );
|
||||
} else if ( mListStyleType == CSSListStyleType::Circle ) {
|
||||
Float radius = fontSize * 0.2f;
|
||||
Float lineWidth = fontSize * 0.04f;
|
||||
Float markerX = std::floor( mScreenPos.x + mPaddingPx.Left - radius * 2.f - offset );
|
||||
Float markerY = std::floor( lineTop + ( lineHeight - radius * 2.f ) * 0.5f + radius );
|
||||
Primitives p;
|
||||
p.setColor( style.FontColor );
|
||||
p.setFillMode( PrimitiveFillMode::DRAW_LINE );
|
||||
p.setLineWidth( lineWidth );
|
||||
p.drawCircle( { markerX, markerY }, radius );
|
||||
} else if ( mListStyleType == CSSListStyleType::Square ) {
|
||||
Float size = fontSize * 0.38f;
|
||||
Float markerX = std::floor( mScreenPos.x + mPaddingPx.Left - size - fontSize * 0.5 );
|
||||
Float markerY = std::floor( lineTop + ( lineHeight - size ) * 0.5f );
|
||||
Primitives p;
|
||||
p.setColor( style.FontColor );
|
||||
p.setFillMode( PrimitiveFillMode::DRAW_FILL );
|
||||
p.drawRectangle( Rectf( markerX, markerY, markerX + size, markerY + size ) );
|
||||
if ( UIHTMLListStyle::isPrimitiveMarker( mListStyleType ) ) {
|
||||
UIHTMLListStyle::drawPrimitiveMarker( mListStyleType, mScreenPos, mPaddingPx, style );
|
||||
} else if ( mListMarkerText && !mListMarkerText->getString().empty() ) {
|
||||
Float markerX =
|
||||
mScreenPos.x + mPaddingPx.Left - mListMarkerText->getTextWidth() - offset;
|
||||
@@ -118,9 +92,7 @@ std::vector<PropertyId> UIHTMLListItem::getPropertiesImplemented() const {
|
||||
}
|
||||
|
||||
void UIHTMLListItem::invalidateList() {
|
||||
if ( mListStyleType == CSSListStyleType::None || mListStyleType == CSSListStyleType::Disc ||
|
||||
mListStyleType == CSSListStyleType::Circle ||
|
||||
mListStyleType == CSSListStyleType::Square ) {
|
||||
if ( !UIHTMLListStyle::isTextMarker( mListStyleType ) ) {
|
||||
mListMarkerText.reset();
|
||||
} else {
|
||||
String::View markerStr = getListMarkerString();
|
||||
@@ -150,51 +122,11 @@ int UIHTMLListItem::countPrecedingLiSiblings() const {
|
||||
String::View UIHTMLListItem::getListMarkerString() const {
|
||||
static String sBuf;
|
||||
|
||||
switch ( mListStyleType ) {
|
||||
case CSSListStyleType::None:
|
||||
case CSSListStyleType::Disc:
|
||||
case CSSListStyleType::Circle:
|
||||
case CSSListStyleType::Square:
|
||||
return {};
|
||||
case CSSListStyleType::Decimal: {
|
||||
int idx = countPrecedingLiSiblings() + 1;
|
||||
sBuf = String( String::toString( idx ) + ". " );
|
||||
return sBuf.view();
|
||||
}
|
||||
case CSSListStyleType::LowerAlpha: {
|
||||
int idx = countPrecedingLiSiblings();
|
||||
char c = 'a' + ( idx % 26 );
|
||||
sBuf = String( 1, (String::StringBaseType)c ) + ". ";
|
||||
return sBuf.view();
|
||||
}
|
||||
case CSSListStyleType::UpperAlpha: {
|
||||
int idx = countPrecedingLiSiblings();
|
||||
char c = 'A' + ( idx % 26 );
|
||||
sBuf = String( 1, (String::StringBaseType)c ) + ". ";
|
||||
return sBuf.view();
|
||||
}
|
||||
case CSSListStyleType::LowerRoman: {
|
||||
static const char* numerals[] = { "i", "ii", "iii", "iv", "v", "vi",
|
||||
"vii", "viii", "ix", "x", "xi", "xii" };
|
||||
int idx = countPrecedingLiSiblings();
|
||||
if ( idx < 12 )
|
||||
sBuf = String( numerals[idx] ) + ". ";
|
||||
else
|
||||
sBuf = String( String::toString( idx + 1 ) + ". " );
|
||||
return sBuf.view();
|
||||
}
|
||||
case CSSListStyleType::UpperRoman: {
|
||||
static const char* numerals[] = { "I", "II", "III", "IV", "V", "VI",
|
||||
"VII", "VIII", "IX", "X", "XI", "XII" };
|
||||
int idx = countPrecedingLiSiblings();
|
||||
if ( idx < 12 )
|
||||
sBuf = String( numerals[idx] ) + ". ";
|
||||
else
|
||||
sBuf = String( String::toString( idx + 1 ) + ". " );
|
||||
return sBuf.view();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
if ( !UIHTMLListStyle::isTextMarker( mListStyleType ) )
|
||||
return {};
|
||||
|
||||
sBuf = UIHTMLListStyle::getTextMarkerString( mListStyleType, countPrecedingLiSiblings() );
|
||||
return sBuf.view();
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
115
src/eepp/ui/uihtmlliststyle.cpp
Normal file
115
src/eepp/ui/uihtmlliststyle.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <eepp/graphics/font.hpp>
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/ui/uihtmlliststyle.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
bool UIHTMLListStyle::isPrimitiveMarker( CSSListStyleType type ) {
|
||||
switch ( type ) {
|
||||
case CSSListStyleType::Disc:
|
||||
case CSSListStyleType::Circle:
|
||||
case CSSListStyleType::Square:
|
||||
case CSSListStyleType::DisclosureClosed:
|
||||
case CSSListStyleType::DisclosureOpen:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UIHTMLListStyle::isTextMarker( CSSListStyleType type ) {
|
||||
return type != CSSListStyleType::None && !isPrimitiveMarker( type );
|
||||
}
|
||||
|
||||
String UIHTMLListStyle::getTextMarkerString( CSSListStyleType type, int index ) {
|
||||
switch ( type ) {
|
||||
case CSSListStyleType::Decimal:
|
||||
return String( String::toString( index + 1 ) + ". " );
|
||||
case CSSListStyleType::LowerAlpha: {
|
||||
char c = 'a' + ( index % 26 );
|
||||
return String( 1, (String::StringBaseType)c ) + ". ";
|
||||
}
|
||||
case CSSListStyleType::UpperAlpha: {
|
||||
char c = 'A' + ( index % 26 );
|
||||
return String( 1, (String::StringBaseType)c ) + ". ";
|
||||
}
|
||||
case CSSListStyleType::LowerRoman: {
|
||||
static const char* numerals[] = { "i", "ii", "iii", "iv", "v", "vi",
|
||||
"vii", "viii", "ix", "x", "xi", "xii" };
|
||||
return index < 12 ? String( numerals[index] ) + ". "
|
||||
: String( String::toString( index + 1 ) + ". " );
|
||||
}
|
||||
case CSSListStyleType::UpperRoman: {
|
||||
static const char* numerals[] = { "I", "II", "III", "IV", "V", "VI",
|
||||
"VII", "VIII", "IX", "X", "XI", "XII" };
|
||||
return index < 12 ? String( numerals[index] ) + ". "
|
||||
: String( String::toString( index + 1 ) + ". " );
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void UIHTMLListStyle::drawPrimitiveMarker( CSSListStyleType type, const Vector2f& screenPos,
|
||||
const Rectf& paddingPx,
|
||||
const Graphics::FontStyleConfig& style ) {
|
||||
Float fontSize = style.CharacterSize;
|
||||
Float offset = 0.25f * fontSize;
|
||||
Float lineHeight = style.Font ? style.Font->getLineSpacing( (unsigned int)fontSize ) : fontSize;
|
||||
Float lineTop = screenPos.y + paddingPx.Top;
|
||||
Graphics::Primitives p;
|
||||
p.setColor( style.FontColor );
|
||||
|
||||
switch ( type ) {
|
||||
case CSSListStyleType::Disc: {
|
||||
Float radius = fontSize * 0.22f;
|
||||
Float markerX = std::floor( screenPos.x + paddingPx.Left - radius * 2.f - offset );
|
||||
Float markerY = std::floor( lineTop + ( lineHeight - radius * 2.f ) * 0.5f + radius );
|
||||
p.setFillMode( Graphics::PrimitiveFillMode::DRAW_FILL );
|
||||
p.drawCircle( { markerX, markerY }, radius );
|
||||
break;
|
||||
}
|
||||
case CSSListStyleType::Circle: {
|
||||
Float radius = fontSize * 0.2f;
|
||||
Float lineWidth = fontSize * 0.04f;
|
||||
Float markerX = std::floor( screenPos.x + paddingPx.Left - radius * 2.f - offset );
|
||||
Float markerY = std::floor( lineTop + ( lineHeight - radius * 2.f ) * 0.5f + radius );
|
||||
p.setFillMode( Graphics::PrimitiveFillMode::DRAW_LINE );
|
||||
p.setLineWidth( lineWidth );
|
||||
p.drawCircle( { markerX, markerY }, radius );
|
||||
break;
|
||||
}
|
||||
case CSSListStyleType::Square: {
|
||||
Float size = fontSize * 0.38f;
|
||||
Float markerX = std::floor( screenPos.x + paddingPx.Left - size - fontSize * 0.5 );
|
||||
Float markerY = std::floor( lineTop + ( lineHeight - size ) * 0.5f );
|
||||
p.setFillMode( Graphics::PrimitiveFillMode::DRAW_FILL );
|
||||
p.drawRectangle( Rectf( markerX, markerY, markerX + size, markerY + size ) );
|
||||
break;
|
||||
}
|
||||
case CSSListStyleType::DisclosureClosed: {
|
||||
Float size = fontSize * 0.48f;
|
||||
Float cx = std::floor( screenPos.x + paddingPx.Left - fontSize * 0.8f );
|
||||
Float cy = std::floor( lineTop + lineHeight * 0.5f );
|
||||
p.setFillMode( Graphics::PrimitiveFillMode::DRAW_FILL );
|
||||
p.drawTriangle( Triangle2f( { cx - size * 0.35f, cy - size * 0.5f },
|
||||
{ cx - size * 0.35f, cy + size * 0.5f },
|
||||
{ cx + size * 0.35f, cy } ) );
|
||||
break;
|
||||
}
|
||||
case CSSListStyleType::DisclosureOpen: {
|
||||
Float size = fontSize * 0.52f;
|
||||
Float cx = std::floor( screenPos.x + paddingPx.Left - fontSize * 0.8f );
|
||||
Float cy = std::floor( lineTop + lineHeight * 0.5f );
|
||||
p.setFillMode( Graphics::PrimitiveFillMode::DRAW_FILL );
|
||||
p.drawTriangle( Triangle2f( { cx - size * 0.5f, cy - size * 0.25f },
|
||||
{ cx + size * 0.5f, cy - size * 0.25f },
|
||||
{ cx, cy + size * 0.45f } ) );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <eepp/ui/uidropdownlist.hpp>
|
||||
#include <eepp/ui/uidropdownmodellist.hpp>
|
||||
#include <eepp/ui/uigridlayout.hpp>
|
||||
#include <eepp/ui/uihtmldetails.hpp>
|
||||
#include <eepp/ui/uihtmlform.hpp>
|
||||
#include <eepp/ui/uihtmlimage.hpp>
|
||||
#include <eepp/ui/uihtmlinput.hpp>
|
||||
@@ -200,6 +201,8 @@ void UIWidgetCreator::createBaseWidgetList() {
|
||||
return w;
|
||||
};
|
||||
registeredWidget["li"] = UIHTMLListItem::New;
|
||||
registeredWidget["details"] = UIHTMLDetails::New;
|
||||
registeredWidget["summary"] = UIHTMLSummary::New;
|
||||
registeredWidget["pre"] = UIRichText::NewPre;
|
||||
registeredWidget["picture"] = [] { return UITextSpan::NewWithTag( "picture" ); };
|
||||
registeredWidget["img"] = UIHTMLImage::New;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <eepp/graphics/fontfamily.hpp>
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/graphics/renderer/renderer.hpp>
|
||||
#include <eepp/scene/keyevent.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
@@ -12,6 +13,7 @@
|
||||
#include <eepp/ui/tools/htmlformatter.hpp>
|
||||
#include <eepp/ui/tools/uiwidgetinspector.hpp>
|
||||
#include <eepp/ui/uicheckbox.hpp>
|
||||
#include <eepp/ui/uihtmldetails.hpp>
|
||||
#include <eepp/ui/uihtmlinput.hpp>
|
||||
#include <eepp/ui/uihtmltable.hpp>
|
||||
#include <eepp/ui/uihtmltextarea.hpp>
|
||||
@@ -580,6 +582,329 @@ UTEST( UIHTML, DataProperties ) {
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, closedByDefault ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d"><summary id="s">Label</summary><p id="p">Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* summary = sceneNode->getRoot()->find( "s" )->asType<UIHTMLSummary>();
|
||||
auto* content = sceneNode->getRoot()->find( "p" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( summary != nullptr );
|
||||
ASSERT_TRUE( content != nullptr );
|
||||
EXPECT_FALSE( details->isOpen() );
|
||||
EXPECT_TRUE( summary->isVisible() );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, summaryListStyleType ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d">
|
||||
<summary id="s1">Default</summary>
|
||||
<summary id="s2" style="list-style-type: disclosure-open;">Open marker</summary>
|
||||
<summary id="s3" style="list-style-type: decimal;">Decimal marker</summary>
|
||||
</details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
const auto* propDef = StyleSheetSpecification::instance()->getProperty( "list-style-type" );
|
||||
ASSERT_TRUE( propDef != nullptr );
|
||||
|
||||
auto* s1 = sceneNode->getRoot()->find( "s1" )->asType<UIHTMLSummary>();
|
||||
auto* s2 = sceneNode->getRoot()->find( "s2" )->asType<UIHTMLSummary>();
|
||||
auto* s3 = sceneNode->getRoot()->find( "s3" )->asType<UIHTMLSummary>();
|
||||
ASSERT_TRUE( s1 != nullptr );
|
||||
ASSERT_TRUE( s2 != nullptr );
|
||||
ASSERT_TRUE( s3 != nullptr );
|
||||
|
||||
EXPECT_TRUE( s1->getPropertyString( propDef ) == "disclosure-closed" );
|
||||
EXPECT_TRUE( s2->getPropertyString( propDef ) == "disclosure-open" );
|
||||
EXPECT_TRUE( s3->getPropertyString( propDef ) == "decimal" );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, summaryListStyleNoneClearsDefaultPadding ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details>
|
||||
<summary id="default">Default marker</summary>
|
||||
<summary id="none" style="list-style-type: none;">No marker</summary>
|
||||
<summary id="explicit" style="list-style-type: none; padding-left: 7px;">
|
||||
Explicit padding
|
||||
</summary>
|
||||
</details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* defaultSummary = sceneNode->getRoot()->find( "default" )->asType<UIHTMLSummary>();
|
||||
auto* noneSummary = sceneNode->getRoot()->find( "none" )->asType<UIHTMLSummary>();
|
||||
auto* explicitSummary = sceneNode->getRoot()->find( "explicit" )->asType<UIHTMLSummary>();
|
||||
ASSERT_TRUE( defaultSummary != nullptr );
|
||||
ASSERT_TRUE( noneSummary != nullptr );
|
||||
ASSERT_TRUE( explicitSummary != nullptr );
|
||||
|
||||
EXPECT_GT( defaultSummary->getPixelsPadding().Left, 0.f );
|
||||
EXPECT_NEAR( noneSummary->getPixelsPadding().Left, 0.f, 0.5f );
|
||||
EXPECT_NEAR( explicitSummary->getPixelsPadding().Left, 7.f, 0.5f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, inlineBlockSummaryListStyleNoneSize ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
std::string html;
|
||||
FileSystem::fileGet( "assets/html/lobsters_item.html", html );
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->querySelector( ".caches" )->asType<UIHTMLDetails>();
|
||||
auto* summary =
|
||||
sceneNode->getRoot()->querySelector( ".caches summary" )->asType<UIHTMLSummary>();
|
||||
auto* author = sceneNode->getRoot()->querySelector( ".u-author" )->asType<UIWidget>();
|
||||
auto* time = sceneNode->getRoot()->querySelector( "time" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( summary != nullptr );
|
||||
ASSERT_TRUE( author != nullptr );
|
||||
ASSERT_TRUE( time != nullptr );
|
||||
|
||||
EXPECT_EQ( details->getDisplay(), CSSDisplay::InlineBlock );
|
||||
EXPECT_EQ( summary->getListStyleType(), CSSListStyleType::None );
|
||||
EXPECT_NEAR( summary->getPixelsPadding().Left, 0.f, 0.5f );
|
||||
EXPECT_LE( details->getPixelsSize().getHeight(),
|
||||
eemax( author->getPixelsSize().getHeight(), time->getPixelsSize().getHeight() ) +
|
||||
1.f );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, openAttribute ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d" open><summary>Label</summary><p id="p">Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* content = sceneNode->getRoot()->find( "p" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( content != nullptr );
|
||||
EXPECT_TRUE( details->isOpen() );
|
||||
EXPECT_TRUE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, openAttributeExplicitFalse ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d" open="false"><summary>Label</summary><p id="p">Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* content = sceneNode->getRoot()->find( "p" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( content != nullptr );
|
||||
EXPECT_FALSE( details->isOpen() );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, toggleViaMouse ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d"><summary id="s">Label</summary><p id="p">Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* summary = sceneNode->getRoot()->find( "s" )->asType<UIHTMLSummary>();
|
||||
auto* content = sceneNode->getRoot()->find( "p" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( summary != nullptr );
|
||||
ASSERT_TRUE( content != nullptr );
|
||||
|
||||
summary->onMouseClick( summary->getPixelsPosition().asInt(), EE_BUTTON_LMASK );
|
||||
EXPECT_TRUE( details->isOpen() );
|
||||
EXPECT_TRUE( content->isVisible() );
|
||||
summary->onMouseClick( summary->getPixelsPosition().asInt(), EE_BUTTON_LMASK );
|
||||
EXPECT_FALSE( details->isOpen() );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, toggleViaKeyboard ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d"><summary id="s">Label</summary><p id="p">Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* summary = sceneNode->getRoot()->find( "s" )->asType<UIHTMLSummary>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( summary != nullptr );
|
||||
|
||||
KeyEvent enter( summary, Event::KeyDown, KEY_RETURN, SCANCODE_RETURN, 0, 0 );
|
||||
KeyEvent space( summary, Event::KeyDown, KEY_SPACE, SCANCODE_SPACE, 0, 0 );
|
||||
EXPECT_EQ( summary->onKeyDown( enter ), 1u );
|
||||
EXPECT_TRUE( details->isOpen() );
|
||||
EXPECT_EQ( summary->onKeyDown( space ), 1u );
|
||||
EXPECT_FALSE( details->isOpen() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, toggleEvent ) {
|
||||
init_ui_test();
|
||||
auto* details = UIHTMLDetails::New();
|
||||
details->setParent( SceneManager::instance()->getUISceneNode()->getRoot() );
|
||||
int toggleCount = 0;
|
||||
details->on( Event::OnToggle, [&toggleCount]( const Event* ) { toggleCount++; } );
|
||||
|
||||
details->setOpen( true );
|
||||
details->setOpen( true );
|
||||
details->setOpen( false );
|
||||
EXPECT_EQ( toggleCount, 2 );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, autoSummary ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d"><p id="p">Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* content = sceneNode->getRoot()->find( "p" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( content != nullptr );
|
||||
auto* summary = details->findSummaryChild();
|
||||
ASSERT_TRUE( summary != nullptr );
|
||||
EXPECT_TRUE( summary->isVisible() );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
EXPECT_TRUE( summary->toggleParentDetails() );
|
||||
EXPECT_TRUE( details->isOpen() );
|
||||
EXPECT_TRUE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, multipleSummaries ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d">
|
||||
<summary id="s1">One</summary>
|
||||
<summary id="s2">Two</summary>
|
||||
<p id="p">Content</p>
|
||||
</details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* s1 = sceneNode->getRoot()->find( "s1" )->asType<UIHTMLSummary>();
|
||||
auto* s2 = sceneNode->getRoot()->find( "s2" )->asType<UIHTMLSummary>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( s1 != nullptr );
|
||||
ASSERT_TRUE( s2 != nullptr );
|
||||
EXPECT_TRUE( s1->isVisible() );
|
||||
EXPECT_FALSE( s2->isVisible() );
|
||||
EXPECT_TRUE( s1->toggleParentDetails() );
|
||||
EXPECT_TRUE( details->isOpen() );
|
||||
EXPECT_TRUE( s2->isVisible() );
|
||||
EXPECT_FALSE( s2->toggleParentDetails() );
|
||||
EXPECT_TRUE( details->isOpen() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, nested ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="outer" open>
|
||||
<summary id="outer_s">Outer</summary>
|
||||
<details id="inner">
|
||||
<summary id="inner_s">Inner</summary>
|
||||
<p id="inner_p">Inner content</p>
|
||||
</details>
|
||||
</details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* outer = sceneNode->getRoot()->find( "outer" )->asType<UIHTMLDetails>();
|
||||
auto* inner = sceneNode->getRoot()->find( "inner" )->asType<UIHTMLDetails>();
|
||||
auto* innerSummary = sceneNode->getRoot()->find( "inner_s" )->asType<UIHTMLSummary>();
|
||||
ASSERT_TRUE( outer != nullptr );
|
||||
ASSERT_TRUE( inner != nullptr );
|
||||
ASSERT_TRUE( innerSummary != nullptr );
|
||||
EXPECT_TRUE( outer->isOpen() );
|
||||
EXPECT_FALSE( inner->isOpen() );
|
||||
EXPECT_TRUE( innerSummary->toggleParentDetails() );
|
||||
EXPECT_TRUE( outer->isOpen() );
|
||||
EXPECT_TRUE( inner->isOpen() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, hiddenChildPreserved ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( R"html(
|
||||
<details id="d" open><summary>Label</summary><p id="p" hidden>Content</p></details>
|
||||
)html" ) );
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
auto* details = sceneNode->getRoot()->find( "d" )->asType<UIHTMLDetails>();
|
||||
auto* content = sceneNode->getRoot()->find( "p" )->asType<UIWidget>();
|
||||
ASSERT_TRUE( details != nullptr );
|
||||
ASSERT_TRUE( content != nullptr );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
details->setOpen( false );
|
||||
details->setOpen( true );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLDetails, dynamicChildAddedWhileClosed ) {
|
||||
init_ui_test();
|
||||
auto* details = UIHTMLDetails::New();
|
||||
details->setParent( SceneManager::instance()->getUISceneNode()->getRoot() );
|
||||
auto* summary = UIHTMLSummary::New();
|
||||
summary->setParent( details );
|
||||
details->setOpen( false );
|
||||
auto* content = UIRichText::NewParagraph();
|
||||
content->setParent( details );
|
||||
details->updateLayout();
|
||||
|
||||
EXPECT_TRUE( summary->isVisible() );
|
||||
EXPECT_FALSE( content->isVisible() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UIHTMLTextArea, rowsColsAttribute ) {
|
||||
init_ui_test();
|
||||
auto* scene = SceneManager::instance()->getUISceneNode();
|
||||
@@ -883,6 +1208,34 @@ UTEST( UILayout, listStyleTypeDisc ) {
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UILayout, listStyleTypeDisclosure ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
sceneNode->loadLayoutFromString( R"html(
|
||||
<html>
|
||||
<ul>
|
||||
<li id="li1" style="list-style-type: disclosure-closed;">Closed</li>
|
||||
<li id="li2" style="list-style-type: disclosure-open;">Open</li>
|
||||
</ul>
|
||||
</html>
|
||||
)html" );
|
||||
|
||||
sceneNode->updateDirtyLayouts();
|
||||
|
||||
const auto* propDef = StyleSheetSpecification::instance()->getProperty( "list-style-type" );
|
||||
ASSERT_TRUE( propDef != nullptr );
|
||||
|
||||
auto* li1 = sceneNode->getRoot()->find( "li1" )->asType<UIRichText>();
|
||||
auto* li2 = sceneNode->getRoot()->find( "li2" )->asType<UIRichText>();
|
||||
ASSERT_TRUE( li1 != nullptr );
|
||||
ASSERT_TRUE( li2 != nullptr );
|
||||
|
||||
EXPECT_TRUE( li1->getPropertyString( propDef ) == "disclosure-closed" );
|
||||
EXPECT_TRUE( li2->getPropertyString( propDef ) == "disclosure-open" );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST( UILayout, listStyleShorthand ) {
|
||||
init_ui_test();
|
||||
auto* sceneNode = SceneManager::instance()->getUISceneNode();
|
||||
|
||||
Reference in New Issue
Block a user