Fix br element tag.

Fix gumbo dependency in ecode.
Fix crash in StyleSheet::getStyleSheetStyleByAtRule.
Plus some minor nits.
This commit is contained in:
Martín Lucas Golini
2026-03-29 20:18:19 -03:00
parent b7726b8767
commit 0f8bacf6dd
14 changed files with 164 additions and 43 deletions

View File

@@ -162,10 +162,6 @@ markdownview a:hover {
color: var(--font-highlight);
}
br {
layout-height: 0;
}
img {
scale-type: fit-inside;
layout-width: wrap_content;
@@ -191,6 +187,10 @@ markdownview table > thead > tr > th {
font-style: bold;
}
markdownview CodeEditor {
padding: 4dp;
}
blockquote {
padding-left: 8dp;
background-color: var(--list-back);

View File

@@ -120,6 +120,7 @@ enum UINodeType {
UI_TYPE_HTML_TABLE_CELL,
UI_TYPE_DROPDOWNMODELLIST,
UI_TYPE_DIFF_VIEW,
UI_TYPE_BR,
UI_TYPE_MODULES = 10000,
UI_TYPE_TERMINAL = 10001,
UI_TYPE_USER = 200000,

View File

@@ -26,7 +26,7 @@ class EE_API UIRichText : public UILayout {
static UIRichText* NewH6() { return UIRichText::NewWithTag( "h6" ); };
static UIRichText* NewBr() { return UIRichText::NewWithTag( "br" ); };
static UIRichText* NewBr();
static UIRichText* NewDiv() { return UIRichText::NewWithTag( "div" ); };

View File

@@ -1726,7 +1726,10 @@ solution "eepp"
language "C++"
files { "src/tools/ecode/**.cpp" }
includedirs { "src/thirdparty/efsw/include", "src/thirdparty", "src/modules/eterm/include/", "src/modules/languages-syntax-highlighting/src" }
links { "efsw-static", "eterm-static", "languages-syntax-highlighting-static", "libyaml-static", "gumbo-parser-static" }
links { "efsw-static", "eterm-static", "languages-syntax-highlighting-static", "libyaml-static" }
if os.is("windows") and is_vs() then
links { "gumbo-parser-static" }
end
if not os.is("windows") and not os.is("haiku") then
links { "pthread" }
end

View File

@@ -1605,12 +1605,13 @@ workspace "eepp"
language "C++"
files { "src/tools/ecode/**.cpp" }
incdirs { "src/thirdparty/efsw/include", "src/thirdparty", "src/modules/eterm/include/", "src/modules/languages-syntax-highlighting/src" }
links { "efsw-static", "eterm-static", "languages-syntax-highlighting-static", "libyaml-static", "gumbo-parser-static" }
links { "efsw-static", "eterm-static", "languages-syntax-highlighting-static", "libyaml-static" }
build_link_configuration( "ecode", false )
filter { "system:windows", "action:not vs*" }
buildoptions{ "-Wa,-mbig-obj" }
linkoptions { "-Wl,--export-all-symbols" }
filter { "system:windows", "action:vs*" }
links { "gumbo-parser-static" }
files { "bin/assets/icon/ecode.rc", "bin/assets/icon/ecode.ico" }
vpaths { ['Resources/*'] = { "ecode.rc", "ecode.ico" } }
filter { "system:windows", "action:not vs*", "architecture:x86" }

View File

@@ -365,22 +365,18 @@ void StyleSheet::addMediaQueryList( MediaQueryList::ptr list ) {
}
}
StyleSheetStyleVector StyleSheet::getStyleSheetStyleByAtRule( const AtRuleType& atRuleType ) const {
StyleSheetStyleVector
StyleSheet::getStyleSheetStyleByAtRule( const AtRuleType& atRuleType ) const {
StyleSheetStyleVector vector;
for ( auto& node : mNodes )
if ( node->getAtRuleType() == atRuleType )
vector.push_back( node.get() );
std::sort( vector.begin(), vector.end(),
[]( const StyleSheetStyle* left, const StyleSheetStyle* right ) {
bool leftHasIt = left->hasProperty( PropertyId::FontStyle );
bool rightHasIt = right->hasProperty( PropertyId::FontStyle );
if ( leftHasIt && !rightHasIt )
return false;
if ( !leftHasIt && rightHasIt )
return true;
return leftHasIt && rightHasIt;
} );
std::sort( vector.begin(), vector.end(), []( const auto& left, const auto& right ) {
bool leftHasIt = left->hasProperty( PropertyId::FontStyle );
bool rightHasIt = right->hasProperty( PropertyId::FontStyle );
return leftHasIt < rightHasIt;
} );
return vector;
}

View File

@@ -4,6 +4,7 @@
#include <eepp/ui/uimenubar.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uithememanager.hpp>
#define PUGIXML_HEADER_ONLY
#include <pugixml/pugixml.hpp>
@@ -15,6 +16,8 @@ UIMenuBar* UIMenuBar::New() {
UIMenuBar::UIMenuBar() :
UIWidget( "menubar" ), mMenuHeight( 0 ), mCurrentMenu( nullptr ), mWaitingUp( nullptr ) {
mFlags |= UI_LOADS_ITS_CHILDREN;
if ( !( mFlags & UI_ANCHOR_RIGHT ) )
mFlags |= UI_ANCHOR_RIGHT;

View File

@@ -14,6 +14,23 @@
namespace EE { namespace UI {
class UILineBreak : public UIRichText {
public:
static UILineBreak* New() { return eeNew( UILineBreak, () ); }
UILineBreak() : UIRichText( "br " ) {}
virtual Uint32 getType() const { return UI_TYPE_BR; }
bool isType( const Uint32& type ) const {
return UILineBreak::getType() == type ? true : UINode::isType( type );
}
};
UIRichText* UIRichText::NewBr() {
return UILineBreak::New();
};
UIRichText* UIRichText::New() {
return eeNew( UIRichText, () );
}
@@ -485,6 +502,9 @@ void UIRichText::rebuildRichText() {
}
spanChild = spanChild->getNextNode();
}
} else if ( widget->isType( UI_TYPE_BR ) ) {
mRichText.addSpan( "\n",
widget->asType<UILineBreak>()->getRichText().getFontStyleConfig() );
} else {
Rectf margin = widget->getLayoutPixelsMargin();
@@ -613,6 +633,15 @@ void UIRichText::positionChildren() {
hitBoxes.clear();
}
} else if ( widget->isType( UI_TYPE_BR ) ) {
curCharIdx += 1;
Vector2f pos;
if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) {
pos = widget->getPrevNode()->asType<UIWidget>()->getPixelsPosition();
pos.y += widget->getPrevNode()->getPixelsSize().getHeight();
}
widget->setPixelsPosition( pos );
widget->setPixelsSize( { mSize.getWidth(), 0 } );
} else {
curCharIdx += 1;
const auto* span = getNextCustomSpan();

View File

@@ -271,6 +271,32 @@ std::vector<UIWidget*> UISceneNode::loadNode( pugi::xml_node node, Node* parent,
for ( pugi::xml_node widget = node; widget; widget = widget.next_sibling() ) {
clock.restart();
if ( String::iequals( widget.name(), "style" ) ) {
CSS::StyleSheetParser parser;
std::string styleContent;
for ( pugi::xml_node child = widget.first_child(); child;
child = child.next_sibling() ) {
if ( child.type() == pugi::node_pcdata || child.type() == pugi::node_cdata ) {
styleContent += child.value();
}
}
if ( parser.loadFromString( std::string_view{ styleContent } ) ) {
parser.getStyleSheet().setMarker( marker );
combineStyleSheet( parser.getStyleSheet(), false );
}
continue;
} else if ( String::iequals( widget.name(), "link" ) ) {
auto type = widget.attribute( "type" );
auto href = widget.attribute( "href" );
auto rel = widget.attribute( "rel" );
if ( !href.empty() && ( String::iequals( type.value(), "text/css" ) ||
String::iequals( rel.value(), "stylesheet" ) ) ) {
loadCSS( href.as_string() );
}
continue;
}
UIWidget* uiwidget = UIWidgetCreator::createFromName( widget.name() );
if ( NULL != uiwidget ) {
@@ -303,28 +329,6 @@ std::vector<UIWidget*> UISceneNode::loadNode( pugi::xml_node node, Node* parent,
}
uiwidget->onWidgetCreated();
} else if ( String::iequals( widget.name(), "style" ) ) {
CSS::StyleSheetParser parser;
std::string styleContent;
for ( pugi::xml_node child = widget.first_child(); child;
child = child.next_sibling() ) {
if ( child.type() == pugi::node_pcdata || child.type() == pugi::node_cdata ) {
styleContent += child.value();
}
}
if ( parser.loadFromString( std::string_view{ styleContent } ) ) {
parser.getStyleSheet().setMarker( marker );
combineStyleSheet( parser.getStyleSheet(), false );
}
} else if ( String::iequals( widget.name(), "link" ) ) {
auto type = widget.attribute( "type" );
auto href = widget.attribute( "href" );
auto rel = widget.attribute( "rel" );
if ( !href.empty() && ( String::iequals( type.value(), "text/css" ) ||
String::iequals( rel.value(), "stylesheet" ) ) ) {
loadCSS( href.as_string() );
}
}
}

View File

@@ -162,11 +162,16 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["pre"] = UIRichText::NewPre;
registeredWidget["img"] = [] { return UIImage::NewWithTag( "img" ); };
registeredWidget["input"] = UITextInput::New;
registeredWidget["header"] = [] { return UIRichText::NewWithTag( "header" ); };
registeredWidget["article"] = [] { return UIRichText::NewWithTag( "article" ); };
registeredWidget["footer"] = [] { return UIRichText::NewWithTag( "footer" ); };
registeredWidget["main"] = [] { return UIRichText::NewWithTag( "main" ); };
registeredWidget["nav"] = [] { return UIRichText::NewWithTag( "nav" ); };
registeredWidget["center"] = [] { return UIRichText::NewWithTag( "center" ); };
registeredWidget["html"] = [] { return UIRichText::NewWithTag( "html" ); };
registeredWidget["head"] = [] { return UIWidget::NewWithTag( "head" ); };
registeredWidget["body"] = [] { return UIRichText::NewWithTag( "body" ); };
registeredWidget["form"] = [] { return UIRichText::NewWithTag( "form" ); };
registeredWidget["table"] = UIHTMLTable::New;
registeredWidget["tr"] = UIHTMLTableRow::New;
registeredWidget["thead"] = UIHTMLTableHead::New;
@@ -182,6 +187,9 @@ void UIWidgetCreator::createBaseWidgetList() {
UIWidget* UIWidgetCreator::createFromName( const std::string& widgetName ) {
createBaseWidgetList();
if ( widgetName.empty() )
return nullptr;
std::string lwidgetName( String::toLower( widgetName ) );
if ( registeredWidget.find( lwidgetName ) != registeredWidget.end() ) {
@@ -192,7 +200,9 @@ UIWidget* UIWidgetCreator::createFromName( const std::string& widgetName ) {
return widgetCallback[lwidgetName]( lwidgetName );
}
return NULL;
eePRINTL( "UIWidgetCreator::createFromName: \"%s\" not found", widgetName.c_str() );
return nullptr;
}
void UIWidgetCreator::addCustomWidgetCallback( const std::string& widgetName,

View File

@@ -10,6 +10,12 @@ EE_MAIN_FUNC int main( int, char** ) {
auto win = app.getWindow();
auto ui = app.getUI();
FontTrueType* remixIconFont = FontTrueType::New( "icon", "assets/fonts/remixicon.ttf" );
FontTrueType* noniconsFont = FontTrueType::New( "nonicons", "assets/fonts/nonicons.ttf" );
FontTrueType* codIconFont = FontTrueType::New( "codicon", "assets/fonts/codicon.ttf" );
ui->getUIIconThemeManager()->setCurrentTheme(
IconManager::init( "icons", remixIconFont, noniconsFont, codIconFont ) );
ui->setColorSchemePreference( ColorSchemeExtPreference::Light );
ui->loadLayoutFromString( R"xml(

View File

@@ -921,7 +921,7 @@ UTEST( UIRichText, MarginsTest ) {
ASSERT_TRUE( d2 != nullptr );
sceneNode->update( Time::Zero );
// Check the layout position of the first div
Vector2f pos1 = d1->getPixelsPosition();
// margin left is 40px, top is 10px, so position inside richtext should be (40, 10)
@@ -933,7 +933,7 @@ UTEST( UIRichText, MarginsTest ) {
Vector2f pos2 = d2->getPixelsPosition();
// The widgets flow inline (horizontally) since total width < 800.
// d1 footprint width: 40 (left) + 50 (width) + 20 (right) = 110.
// d2 left margin: 5.
// d2 left margin: 5.
// Therefore d2 x position = 110 + 5 = 115.
// Line height is determined by max footprint height.
// d1 footprint height: 10 + 50 + 30 = 90.
@@ -954,3 +954,71 @@ UTEST( UIRichText, MarginsTest ) {
eeDelete( sceneNode );
Engine::destroySingleton();
}
UTEST( UIRichText, ForcedLineBreak ) {
Engine::instance()->createWindow( WindowSettings( 800, 600, "BR Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false,
true ) );
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
ASSERT_TRUE( font->loaded() );
FontFamily::loadFromRegular( font );
UISceneNode* sceneNode = UISceneNode::New();
sceneNode->getUIThemeManager()->setDefaultFont( font );
String xml = R"xml(<richtext id="rt">Line 1<br/>Line 2</richtext>)xml";
sceneNode->loadLayoutFromString( xml );
UIRichText* rt = sceneNode->find<UIRichText>( "rt" );
ASSERT_TRUE( rt != nullptr );
sceneNode->update( Time::Zero );
const auto& richText = rt->getRichText();
EXPECT_EQ( richText.getLines().size(), (size_t)3 );
eeDelete( sceneNode );
Engine::destroySingleton();
}
UTEST( UIRichText, CustomBRHeight ) {
Engine::instance()->createWindow( WindowSettings( 800, 600, "BR Test", WindowStyle::Default,
WindowBackend::Default, 32, {}, 1, false,
true ) );
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
ASSERT_TRUE( font->loaded() );
FontFamily::loadFromRegular( font );
UISceneNode* sceneNode = UISceneNode::New();
sceneNode->getUIThemeManager()->setDefaultFont( font );
String xml = R"xml(<richtext id="rt">Line 1<br font-size="50px"/>Line 2</richtext>)xml";
sceneNode->loadLayoutFromString( xml );
UIRichText* rt = sceneNode->find<UIRichText>( "rt" );
ASSERT_TRUE( rt != nullptr );
sceneNode->update( Time::Zero );
const auto& richText = rt->getRichText();
const auto& lines = richText.getLines();
EXPECT_EQ( lines.size(), (size_t)3 );
if ( lines.size() >= 2 ) {
EXPECT_GT( lines[0].height, lines[1].height );
EXPECT_GT( lines[2].height, 0.f );
}
eeDelete( sceneNode );
Engine::destroySingleton();
}