mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Several general improvements and fixes across the UI: UIRichText, UITextSpan, UIImage, UIScrollView.
This commit is contained in:
@@ -1,11 +1,30 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
# Build Instructions (Debug Mode)
|
||||
|
||||
To build the project in debug mode you must run from the root project directory:
|
||||
All build commands must be executed from the **root project directory**. Follow these steps to build the project:
|
||||
|
||||
`make -C make/linux -j$(nproc)`
|
||||
## Step 1: Update Makefiles (Conditional)
|
||||
If you have **added, renamed, or deleted** any source files, you must regenerate the makefiles before compiling.
|
||||
|
||||
If any file has been added you should also run (previous to the make command):
|
||||
* **Tool:** Use `premake5` if installed; otherwise, fallback to `premake4` (the parameters are identical).
|
||||
* **Linker Flag (`--with-mold-linker`):** This flag is conditional. If the `mold` linker is installed on the system, you **must** include it to speed up linking. If `mold` is not installed, omit the flag.
|
||||
|
||||
`premake4 --disable-static-build --with-mold-linker --with-debug-symbols --address-sanitizer gmake`
|
||||
**Command (if `mold` is installed):**
|
||||
`premake5 --disable-static-build --with-mold-linker --with-debug-symbols --address-sanitizer gmake`
|
||||
|
||||
**Command (if `mold` is NOT installed):**
|
||||
`premake5 --disable-static-build --with-debug-symbols --address-sanitizer gmake`
|
||||
|
||||
*(If no files were added/removed, you may skip Step 1).*
|
||||
|
||||
## Step 2: Compile the Project
|
||||
To compile the project in debug mode, execute the `make` command, ensuring you point to the correct directory for your current Operating System.
|
||||
|
||||
The valid OS directory names are: `windows`, `macosx`, `linux`, `bsd`, `haiku`.
|
||||
|
||||
Run the following command, replacing `<os_name>` with the correct environment:
|
||||
`make -C make/<os_name> -j$(nproc)`
|
||||
|
||||
**Examples:**
|
||||
* Linux: `make -C make/linux -j$(nproc)`
|
||||
* macOS: `make -C make/macosx -j$(sysctl -n hw.ncpu)`
|
||||
* Windows: `make -C make/windows -j%NUMBER_OF_PROCESSORS%`
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
# Project Architecture: eepp & ecode
|
||||
|
||||
[eepp](https://github.com/SpartanJ/eepp/) is an open source cross-platform game and application development framework heavily focused on the development of rich graphical user interfaces.
|
||||
This repository contains two primary components: a core framework (`eepp`) and an application built on top of it (`ecode`).
|
||||
|
||||
Inside this repository also lives [ecode](https://github.com/SpartanJ/ecode/). ecode is a lightweight multi-platform code editor designed for modern hardware with a focus on responsiveness and performance. It has been developed with the hardware-accelerated eepp GUI, which provides the core technology for the editor. The project comes as the first serious project using the eepp GUI, and it's currently being developed to improve the eepp GUI library as part of one of its main objectives.
|
||||
## 1. eepp (Core Framework)
|
||||
[eepp](https://github.com/SpartanJ/eepp/) is an open-source, cross-platform game and application development framework. It is heavily focused on providing robust technology for rich, hardware-accelerated Graphical User Interfaces (GUIs).
|
||||
|
||||
Very basic eepp documentation can be found at `docs/articles`. Many class headers have Doxygen documentation, rely on that. eepp headers are at `include/eepp/`.
|
||||
## 2. ecode (Application)
|
||||
[ecode](https://github.com/SpartanJ/ecode/) is a lightweight, multi-platform code editor designed for responsiveness and performance.
|
||||
* **Relationship:** `ecode` is built *using* the `eepp` GUI framework. It acts as the primary real-world consumer of `eepp`.
|
||||
* **Goal:** Development on `ecode` is often used to test, improve, and drive new features in the underlying `eepp` library.
|
||||
|
||||
A good amount of examples on how to use the library can be found in `src/examples`.
|
||||
|
||||
The `README.md` at the root directory explains in more detail about the project.
|
||||
## Documentation & Code References
|
||||
When working on this project, rely on the following resources to understand existing implementations:
|
||||
* **C++ Headers (Primary Reference):** Rely heavily on Doxygen documentation found directly inside the class headers located at `include/eepp/`.
|
||||
* **Basic Documentation:** Found in `docs/articles/`.
|
||||
* **Implementation Examples:** A wide variety of examples showing how to use the library are located in `src/examples/`.
|
||||
* **General Context:** The `README.md` at the root directory contains deeper project details.
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
---
|
||||
trigger: always_on
|
||||
---
|
||||
# Unit Testing Requirements & Guidelines
|
||||
|
||||
Project provide a good range of unit-tests that they must pass to guarantee that changes made do not break functionality.
|
||||
To run the tests you must execute the binary:
|
||||
`bin/unit_tests/eepp-unit_tests-debug`
|
||||
This project relies on a comprehensive suite of unit tests to prevent regressions. You must ensure all existing tests pass after making modifications.
|
||||
|
||||
This path is from the root directory, you can run it from anywhere, current working directory is managed by the binary.
|
||||
## Running Tests
|
||||
The test binary manages its own current working directory, so you can execute it from anywhere.
|
||||
|
||||
If you need to run an specific test you can use the filter parameter, it supports glob patterns, for example:
|
||||
* **Standard Execution:**
|
||||
`bin/unit_tests/eepp-unit_tests-debug`
|
||||
* **Linux & FreeBSD Execution (Required for Desktop Environments):**
|
||||
Tests open ~50 individual windows. To prevent disrupting the desktop environment, run them in an isolated framebuffer using `xvfb-run`:
|
||||
`xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests-debug`
|
||||
* **Filtering Tests:**
|
||||
Use the `--filter` parameter to run specific tests (supports glob patterns).
|
||||
*Example (runs all tests with "Offset" in the name):*
|
||||
`bin/unit_tests/eepp-unit_tests-debug --filter="FontRendering.*Offset*"`
|
||||
|
||||
`bin/unit_tests/eepp-unit_tests-debug --filter="FontRendering.*Offset*"`
|
||||
## Writing New Tests
|
||||
Writing new tests is highly encouraged, but depends on the context of your changes:
|
||||
* **Core Framework (`eepp`):** If you add new logic, math, or framework-level features, you are **expected** to write unit tests for them.
|
||||
* **Application/Tools (`ecode`):** Application-level UI changes or tool integrations are often difficult to mock/test. Tests for these are **optional** and should only be added if practical to set up.
|
||||
|
||||
Will run all tests with "Offset" in its name.
|
||||
It's expected that for *any* requested new functionality you must add new tests and also tests with previously existing ones. Initially always test with the most relevant to the change that's has been made.
|
||||
|
||||
Tests can be found at: `src/tests/unit_tests`. Being `src/tests/unit_tests/fontrendering.cpp` the most complete set of tests related to text rendering.
|
||||
**Testing Workflow:**
|
||||
1. All tests are located in `src/tests/unit_tests/`.
|
||||
2. Before modifying code, run the existing tests most relevant to your change to ensure a baseline.
|
||||
3. For reference on how tests are structured in this project, review `src/tests/unit_tests/fontrendering.cpp` (the most complete set of text rendering tests).
|
||||
|
||||
4
TODO.md
4
TODO.md
@@ -9,8 +9,6 @@
|
||||
|
||||
* Implement a better DropDownList (model/view approach)
|
||||
|
||||
* Implement a Rich Text View.
|
||||
|
||||
* Implement TableView and TreeView properties.
|
||||
|
||||
* Add automatic font-fallback for lang scripts
|
||||
@@ -19,8 +17,6 @@
|
||||
|
||||
* Implement support for very simple state-changes from the XML file (ex: onclick="toggleclass(x)").
|
||||
|
||||
* Implement a Markdown View
|
||||
|
||||
* Implement smooth-scrolling for macOS
|
||||
|
||||
* Implement UITabWidgetSplitter
|
||||
|
||||
@@ -135,6 +135,10 @@ a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
br {
|
||||
layout-height: 1em;
|
||||
}
|
||||
|
||||
markdownview img {
|
||||
scale-type: fit-inside;
|
||||
max-height: 100vh;
|
||||
|
||||
@@ -701,7 +701,7 @@ Multiple flags can be set, flags are separated by `|`.
|
||||
* `clip`: Enables clipping of the element box.
|
||||
* `multiselect`: Enables multiple selection on elements that support selection. EE::UI::UIListBox (ListBox) for the moment.
|
||||
* `autopadding`: Enables the element to calculate the padding based on the skin size.
|
||||
* `reportsizechangetochilds`: When enabled the element will emit a event (`OnParentSizeChange`) to its children reporting the size change of the parent.
|
||||
* `reportsizechangetochildren`: When enabled the element will emit a event (`OnParentSizeChange`) to its children reporting the size change of the parent.
|
||||
* Default value: _No value_
|
||||
|
||||
---
|
||||
|
||||
@@ -162,6 +162,8 @@ class EE_API RichText : public Drawable {
|
||||
/** @return The current selection as a string. */
|
||||
String getSelectionString() const;
|
||||
|
||||
void updateLayout();
|
||||
|
||||
protected:
|
||||
std::vector<Block> mBlocks;
|
||||
std::vector<RenderParagraph> mLines;
|
||||
@@ -174,8 +176,6 @@ class EE_API RichText : public Drawable {
|
||||
Sizef mSize;
|
||||
Int64 mTotalCharacterCount{ 0 };
|
||||
bool mNeedsLayoutUpdate{ true };
|
||||
|
||||
void updateLayout();
|
||||
};
|
||||
|
||||
}} // namespace EE::Graphics
|
||||
|
||||
@@ -9,7 +9,31 @@ namespace EE { namespace UI { namespace Doc {
|
||||
|
||||
class EE_API Markdown {
|
||||
public:
|
||||
static std::string toXHTML( std::string_view markdown );
|
||||
enum class Dialect { CommonMark, GitHub };
|
||||
|
||||
enum Flags {
|
||||
CollapseWhitespace =
|
||||
0x0001, /* In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' ' */
|
||||
PermissiveAtxHeaders = 0x0002, /* Do not require space in ATX headers ( ###header ) */
|
||||
PermissiveUrlAutolinks = 0x0004, /* Recognize URLs as autolinks even without '<', '>' */
|
||||
PermissiveEmailAutolinks =
|
||||
0x0008, /* Recognize e-mails as autolinks even without '<', '>' and 'mailto:' */
|
||||
NoIndentedCodeBlocks = 0x0010, /* Disable indented code blocks. (Only fenced code works.) */
|
||||
NoHtmlBlocks = 0x0020, /* Disable raw HTML blocks. */
|
||||
NoHtmlSpans = 0x0040, /* Disable raw HTML (inline). */
|
||||
Tables = 0x0100, /* Enable tables extension. */
|
||||
Strikethrough = 0x0200, /* Enable strikethrough extension. */
|
||||
PermissiveWwwAutolinks = 0x0400, /* Enable WWW autolinks (even without any scheme prefix, if
|
||||
they begin with 'www.') */
|
||||
TaskLists = 0x0800, /* Enable task list extension. */
|
||||
LatexMathSpans = 0x1000, /* Enable $ and $$ containing LaTeX equations. */
|
||||
WikiLinks = 0x2000, /* Enable wiki links extension. */
|
||||
Underline = 0x4000, /* Enable underline extension (and disables '_' for normal emphasis). */
|
||||
HardSoftBreaks = 0x8000, /* Force all soft breaks to act as hard breaks. */
|
||||
};
|
||||
|
||||
static std::string toXHTML( std::string_view markdown, Dialect dialect = Dialect::GitHub,
|
||||
int flags = 0 );
|
||||
};
|
||||
|
||||
}}} // namespace EE::UI::Doc
|
||||
|
||||
@@ -47,11 +47,6 @@ class EE_API UILayout : public UIWidget {
|
||||
|
||||
void setLayoutDirty();
|
||||
|
||||
Sizef getSizeFromLayoutPolicy();
|
||||
|
||||
Float getMatchParentWidth() const;
|
||||
|
||||
Float getMatchParentHeight() const;
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -26,8 +26,16 @@ class EE_API UIRichText : public UILayout {
|
||||
|
||||
static UIRichText* NewH6() { return UIRichText::NewWithTag( "h6" ); };
|
||||
|
||||
static UIRichText* NewBr() { return UIRichText::NewWithTag( "br" ); };
|
||||
|
||||
static UIRichText* NewDiv() { return UIRichText::NewWithTag( "div" ); };
|
||||
|
||||
static UIRichText* NewPre() { return UIRichText::NewWithTag( "pre" ); };
|
||||
|
||||
static UIRichText* NewListItem() { return UIRichText::NewWithTag( "li" ); };
|
||||
|
||||
static UIRichText* NewBlockquote() { return UIRichText::NewWithTag( "blockquote" ); };
|
||||
|
||||
explicit UIRichText( const std::string& tag = "richtext" );
|
||||
|
||||
virtual Uint32 getType() const;
|
||||
@@ -112,10 +120,11 @@ class EE_API UIRichText : public UILayout {
|
||||
Int64 mSelCurInit{ 0 };
|
||||
Int64 mSelCurEnd{ 0 };
|
||||
bool mSelecting{ false };
|
||||
size_t mResizedCount{ 0 };
|
||||
|
||||
virtual Uint32 onMessage( const NodeMessage* Msg );
|
||||
virtual Uint32 onMouseDown( const Vector2i& position, const Uint32& flags );
|
||||
virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
|
||||
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
|
||||
virtual Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags );
|
||||
virtual Uint32 onFocusLoss();
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
|
||||
public:
|
||||
static UIScrollView* New();
|
||||
|
||||
virtual ~UIScrollView();
|
||||
|
||||
virtual Uint32 getType() const;
|
||||
|
||||
virtual bool isType( const Uint32& type ) const;
|
||||
@@ -63,6 +65,9 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
|
||||
bool mAutoSetClipStep{ true };
|
||||
bool mAnchorScroll{ false };
|
||||
Sizef mLastScrollViewSize;
|
||||
Node* mParentRef{ nullptr };
|
||||
Uint32 mParentSizeChangeCb{ 0 };
|
||||
Uint32 mParentCloseCb{ 0 };
|
||||
|
||||
UIScrollView();
|
||||
|
||||
@@ -76,6 +81,8 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
|
||||
|
||||
virtual void onPaddingChange();
|
||||
|
||||
virtual void onSizePolicyChange();
|
||||
|
||||
void onValueChangeCb( const Event* Event );
|
||||
|
||||
void onScrollViewSizeChange( const Event* Event );
|
||||
@@ -89,6 +96,15 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
|
||||
virtual void onTouchDragValueChange( Vector2f diff );
|
||||
|
||||
virtual bool isTouchOverAllowedChildren();
|
||||
|
||||
virtual void onParentChange();
|
||||
|
||||
void listenParent();
|
||||
|
||||
void clearListeners();
|
||||
|
||||
void updateInternalSize();
|
||||
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -165,6 +165,8 @@ class EE_API UIAnchorSpan : public UITextSpan {
|
||||
std::string mHref;
|
||||
|
||||
virtual Uint32 onKeyDown( const KeyEvent& event );
|
||||
|
||||
virtual Uint32 onMessage( const NodeMessage* Msg );
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -1402,6 +1402,14 @@ class EE_API UIWidget : public UINode {
|
||||
*/
|
||||
virtual void onSizeChange();
|
||||
|
||||
/**
|
||||
* @brief Handles size policy change events.
|
||||
*
|
||||
* Called when this widget's size policy changes. This can be overridden to
|
||||
* implement custom handling of size policy changes.
|
||||
*/
|
||||
virtual void onSizePolicyChange();
|
||||
|
||||
/**
|
||||
* @brief Handles auto-size events.
|
||||
*
|
||||
@@ -1623,6 +1631,16 @@ class EE_API UIWidget : public UINode {
|
||||
* Reloads the font family for this widget, typically after a theme change.
|
||||
*/
|
||||
void reloadFontFamily();
|
||||
|
||||
/* @return The width of the widget when size policy is match_parent */
|
||||
Float getMatchParentWidth() const;
|
||||
|
||||
/* @return The height of the widget when size policy is match_parent */
|
||||
Float getMatchParentHeight() const;
|
||||
|
||||
/* @return The size of the widget when size policy is match_parent */
|
||||
Sizef getSizeFromLayoutPolicy();
|
||||
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -8,9 +8,11 @@ static void process_output( const MD_CHAR* text, MD_SIZE size, void* userdata )
|
||||
out->append( text, size );
|
||||
}
|
||||
|
||||
std::string Markdown::toXHTML( std::string_view markdown ) {
|
||||
std::string Markdown::toXHTML( std::string_view markdown, Dialect dialect, int flags ) {
|
||||
std::string out;
|
||||
md_html( markdown.data(), markdown.size(), process_output, &out, MD_DIALECT_GITHUB,
|
||||
int dialectFlag =
|
||||
( dialect == Dialect::CommonMark ? MD_DIALECT_COMMONMARK : MD_DIALECT_GITHUB ) | flags;
|
||||
md_html( markdown.data(), markdown.size(), process_output, &out, dialectFlag,
|
||||
MD_HTML_FLAG_XHTML );
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -216,9 +216,10 @@ void UIImage::onDrawableResourceEvent( DrawableResource::Event event, DrawableRe
|
||||
auto s = mSize;
|
||||
onAutoSize();
|
||||
calcDestSize();
|
||||
|
||||
if ( mSize != s ) {
|
||||
invalidateDraw();
|
||||
notifyLayoutAttrChangeParent();
|
||||
invalidateDraw();
|
||||
}
|
||||
} );
|
||||
} else if ( event == DrawableResource::Unload ) {
|
||||
|
||||
@@ -68,64 +68,6 @@ void UILayout::setLayoutDirty() {
|
||||
}
|
||||
}
|
||||
|
||||
Sizef UILayout::getSizeFromLayoutPolicy() {
|
||||
Sizef size( getPixelsSize() );
|
||||
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::MatchParent ) {
|
||||
Float w = getMatchParentWidth();
|
||||
|
||||
if ( (int)w != (int)getPixelsSize().getWidth() )
|
||||
size.setWidth( w );
|
||||
}
|
||||
|
||||
if ( getLayoutHeightPolicy() == SizePolicy::MatchParent ) {
|
||||
Float h = getMatchParentHeight();
|
||||
|
||||
if ( (int)h != (int)getPixelsSize().getHeight() )
|
||||
size.setHeight( h );
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
Float UILayout::getMatchParentWidth() const {
|
||||
Rectf padding = Rectf();
|
||||
|
||||
if ( getParent()->isWidget() )
|
||||
padding = static_cast<UIWidget*>( getParent() )->getPixelsPadding();
|
||||
|
||||
Float width = getParent()->getPixelsSize().getWidth() - mLayoutMarginPx.Left -
|
||||
mLayoutMarginPx.Right - padding.Left - padding.Right;
|
||||
|
||||
if ( !mMaxWidthEq.empty() ) {
|
||||
Float maxWidth( getMaxSizePx().getWidth() - mLayoutMarginPx.Left - mLayoutMarginPx.Right -
|
||||
padding.Left - padding.Right );
|
||||
if ( maxWidth > 0 && maxWidth < width )
|
||||
width = maxWidth;
|
||||
}
|
||||
|
||||
return eemax( 0.f, width );
|
||||
}
|
||||
|
||||
Float UILayout::getMatchParentHeight() const {
|
||||
Rectf padding = Rectf();
|
||||
|
||||
if ( getParent()->isWidget() )
|
||||
padding = static_cast<UIWidget*>( getParent() )->getPadding();
|
||||
|
||||
Float height = getParent()->getPixelsSize().getHeight() - mLayoutMarginPx.Top -
|
||||
mLayoutMarginPx.Bottom - padding.Top - padding.Bottom;
|
||||
|
||||
if ( !mMaxHeightEq.empty() ) {
|
||||
Float maxHeight( getMaxSizePx().getHeight() - mLayoutMarginPx.Left - mLayoutMarginPx.Right -
|
||||
padding.Left - padding.Right );
|
||||
if ( maxHeight > 0 && maxHeight < height )
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
return eemax( 0.f, height );
|
||||
}
|
||||
|
||||
bool UILayout::isGravityOwner() const {
|
||||
return mGravityOwner;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ bool UIMarkdownView::isType( const Uint32& type ) const {
|
||||
|
||||
void UIMarkdownView::loadFromString( std::string_view markdown ) {
|
||||
closeAllChildren();
|
||||
std::string xhtml = Markdown::toXHTML( markdown );
|
||||
auto xhtml = Markdown::toXHTML( markdown );
|
||||
// printf( "%s", xhtml.c_str() );
|
||||
getUISceneNode()->loadLayoutFromString( xhtml, this );
|
||||
}
|
||||
|
||||
|
||||
@@ -577,7 +577,10 @@ void UIRichText::positionChildren() {
|
||||
Vector2f boundsPos = bounds.getPosition();
|
||||
|
||||
widget->setPixelsPosition( boundsPos - offset );
|
||||
widget->setPixelsSize( bounds.getSize() );
|
||||
if ( bounds.getSize() != widget->getPixelsSize() ) {
|
||||
widget->setPixelsSize( bounds.getSize() );
|
||||
mResizedCount++;
|
||||
}
|
||||
|
||||
for ( auto& hb : hitBoxes )
|
||||
hb.move( -boundsPos );
|
||||
@@ -626,11 +629,12 @@ void UIRichText::updateDefaultSpansStyle() {
|
||||
void UIRichText::updateLayout() {
|
||||
if ( mPacking )
|
||||
return;
|
||||
mResizedCount = 0;
|
||||
mPacking = true;
|
||||
|
||||
rebuildRichText();
|
||||
|
||||
mRichText.getSize(); // Forces an updateLayout internally
|
||||
mRichText.updateLayout();
|
||||
|
||||
positionChildren();
|
||||
|
||||
@@ -643,8 +647,12 @@ void UIRichText::updateLayout() {
|
||||
mPaddingPx.Bottom );
|
||||
}
|
||||
|
||||
if ( mResizedCount )
|
||||
positionChildren();
|
||||
|
||||
mPacking = false;
|
||||
mDirtyLayout = false;
|
||||
mResizedCount = 0;
|
||||
}
|
||||
|
||||
Uint32 UIRichText::onMessage( const NodeMessage* Msg ) {
|
||||
@@ -653,6 +661,24 @@ Uint32 UIRichText::onMessage( const NodeMessage* Msg ) {
|
||||
tryUpdateLayout();
|
||||
return 1;
|
||||
}
|
||||
case NodeMessage::MouseDown: {
|
||||
if ( Msg->getSender()->isType( UI_TYPE_TEXTSPAN ) )
|
||||
return onMouseDown( getEventDispatcher()->getMousePos(), Msg->getFlags() );
|
||||
}
|
||||
case NodeMessage::MouseUp: {
|
||||
if ( Msg->getSender()->isType( UI_TYPE_TEXTSPAN ) ) {
|
||||
onMouseUp( getEventDispatcher()->getMousePos(), Msg->getFlags() );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
case NodeMessage::MouseClick: {
|
||||
if ( Msg->getSender()->isType( UI_TYPE_TEXTSPAN ) )
|
||||
return onMouseClick( getEventDispatcher()->getMousePos(), Msg->getFlags() );
|
||||
}
|
||||
case NodeMessage::MouseDoubleClick: {
|
||||
if ( Msg->getSender()->isType( UI_TYPE_TEXTSPAN ) )
|
||||
return onMouseDoubleClick( getEventDispatcher()->getMousePos(), Msg->getFlags() );
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -704,7 +730,8 @@ String UIRichText::getSelectionString() const {
|
||||
|
||||
Uint32 UIRichText::onMouseDown( const Vector2i& position, const Uint32& flags ) {
|
||||
if ( NULL != getEventDispatcher() && isTextSelectionEnabled() && ( flags & EE_BUTTON_LMASK ) &&
|
||||
getEventDispatcher()->getMouseDownNode() == this ) {
|
||||
( getEventDispatcher()->getMouseDownNode() == this ||
|
||||
inParentTreeOf( getEventDispatcher()->getMouseDownNode() ) ) ) {
|
||||
Vector2f nodePos( Vector2f( position.x, position.y ) );
|
||||
worldToNode( nodePos );
|
||||
nodePos = PixelDensity::dpToPx( nodePos ) - Vector2f( mPaddingPx.Left, mPaddingPx.Top );
|
||||
@@ -730,7 +757,7 @@ Uint32 UIRichText::onMouseDown( const Vector2i& position, const Uint32& flags )
|
||||
return UILayout::onMouseDown( position, flags );
|
||||
}
|
||||
|
||||
Uint32 UIRichText::onMouseClick( const Vector2i& position, const Uint32& flags ) {
|
||||
Uint32 UIRichText::onMouseUp( const Vector2i& position, const Uint32& flags ) {
|
||||
if ( isTextSelectionEnabled() && ( flags & EE_BUTTON_LMASK ) ) {
|
||||
mSelecting = false;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,64 @@ UIScrollView::UIScrollView() :
|
||||
mHScroll->on( Event::OnValueChange, [this]( auto event ) { onValueChangeCb( event ); } );
|
||||
|
||||
applyDefaultTheme();
|
||||
listenParent();
|
||||
}
|
||||
|
||||
UIScrollView::~UIScrollView() {
|
||||
clearListeners();
|
||||
}
|
||||
|
||||
void UIScrollView::updateInternalSize() {
|
||||
Sizef size( getSizeFromLayoutPolicy() );
|
||||
if ( size != getPixelsSize() )
|
||||
setInternalPixelsSize( size );
|
||||
}
|
||||
|
||||
void UIScrollView::listenParent() {
|
||||
clearListeners();
|
||||
|
||||
mParentRef = getParent();
|
||||
|
||||
if ( !mParentRef->isLayout() ) {
|
||||
mParentSizeChangeCb = mParentRef->on( Event::OnSizeChange, [this]( const Event* ) {
|
||||
if ( !getParent()->isLayout() &&
|
||||
( getLayoutWidthPolicy() == SizePolicy::MatchParent ||
|
||||
getLayoutHeightPolicy() == SizePolicy::MatchParent ) &&
|
||||
getParent()->getPixelsSize() != Sizef::Zero &&
|
||||
getParent()->getPixelsSize() != mSize ) {
|
||||
runOnMainThread( [this]() { updateInternalSize(); } );
|
||||
}
|
||||
} );
|
||||
|
||||
mParentCloseCb =
|
||||
mParentRef->on( Event::OnClose, [this]( const Event* ) { mParentRef = nullptr; } );
|
||||
|
||||
updateInternalSize();
|
||||
}
|
||||
}
|
||||
|
||||
void UIScrollView::onParentChange() {
|
||||
listenParent();
|
||||
}
|
||||
|
||||
void UIScrollView::clearListeners() {
|
||||
if ( mParentRef ) {
|
||||
if ( mParentSizeChangeCb > 0 ) {
|
||||
mParentRef->removeEventListener( mParentSizeChangeCb );
|
||||
mParentSizeChangeCb = 0;
|
||||
}
|
||||
if ( mParentCloseCb > 0 ) {
|
||||
mParentRef->removeEventListener( mParentCloseCb );
|
||||
mParentCloseCb = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UIScrollView::onSizePolicyChange() {
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::MatchParent ||
|
||||
getLayoutHeightPolicy() == SizePolicy::MatchParent ) {
|
||||
updateInternalSize();
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 UIScrollView::getType() const {
|
||||
|
||||
@@ -544,13 +544,17 @@ UIAnchorSpan* UIAnchorSpan::New() {
|
||||
return eeNew( UIAnchorSpan, () );
|
||||
}
|
||||
|
||||
UIAnchorSpan::UIAnchorSpan( const std::string& tag ) : UITextSpan( tag ) {
|
||||
onClick(
|
||||
[this]( const MouseEvent* ) {
|
||||
if ( !mHref.empty() )
|
||||
UIAnchorSpan::UIAnchorSpan( const std::string& tag ) : UITextSpan( tag ) {}
|
||||
|
||||
Uint32 UIAnchorSpan::onMessage( const NodeMessage* Msg ) {
|
||||
switch ( Msg->getMsg() ) {
|
||||
case NodeMessage::MouseClick: {
|
||||
if ( !mHref.empty() && ( Msg->getFlags() & EE_BUTTON_LMASK ) )
|
||||
Engine::instance()->openURI( mHref );
|
||||
},
|
||||
EE_BUTTON_LEFT );
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return UITextSpan::onMessage( Msg );
|
||||
}
|
||||
|
||||
bool UIAnchorSpan::applyProperty( const StyleSheetProperty& attribute ) {
|
||||
|
||||
@@ -250,6 +250,7 @@ UIWidget* UIWidget::setLayoutWidthPolicy( const SizePolicy& widthPolicy ) {
|
||||
mWidthPolicy = widthPolicy;
|
||||
if ( mWidthPolicy == SizePolicy::WrapContent )
|
||||
onAutoSize();
|
||||
onSizePolicyChange();
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
|
||||
@@ -265,6 +266,7 @@ UIWidget* UIWidget::setLayoutHeightPolicy( const SizePolicy& heightPolicy ) {
|
||||
mHeightPolicy = heightPolicy;
|
||||
if ( mHeightPolicy == SizePolicy::WrapContent )
|
||||
onAutoSize();
|
||||
onSizePolicyChange();
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
|
||||
@@ -279,6 +281,7 @@ UIWidget* UIWidget::setLayoutSizePolicy( const SizePolicy& widthPolicy,
|
||||
if ( mWidthPolicy == SizePolicy::WrapContent || mHeightPolicy == SizePolicy::WrapContent ) {
|
||||
onAutoSize();
|
||||
}
|
||||
onSizePolicyChange();
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
|
||||
@@ -561,6 +564,8 @@ void UIWidget::onSizeChange() {
|
||||
notifyLayoutAttrChange();
|
||||
}
|
||||
|
||||
void UIWidget::onSizePolicyChange() {}
|
||||
|
||||
void UIWidget::onAutoSize() {}
|
||||
|
||||
void UIWidget::onWidgetCreated() {}
|
||||
@@ -2089,7 +2094,7 @@ std::string UIWidget::getFlagsString() const {
|
||||
if ( mFlags & UI_AUTO_PADDING )
|
||||
flagvec.push_back( "autopadding" );
|
||||
if ( reportSizeChangeToChildren() )
|
||||
flagvec.push_back( "reportsizechangetochilds" );
|
||||
flagvec.push_back( "reportsizechangetochildren" );
|
||||
if ( isClipped() )
|
||||
flagvec.push_back( "clip" );
|
||||
|
||||
@@ -2290,4 +2295,62 @@ void UIWidget::onFocusNextWidget() {
|
||||
}
|
||||
}
|
||||
|
||||
Float UIWidget::getMatchParentWidth() const {
|
||||
Rectf padding = Rectf();
|
||||
|
||||
if ( getParent()->isWidget() )
|
||||
padding = static_cast<UIWidget*>( getParent() )->getPixelsPadding();
|
||||
|
||||
Float width = getParent()->getPixelsSize().getWidth() - mLayoutMarginPx.Left -
|
||||
mLayoutMarginPx.Right - padding.Left - padding.Right;
|
||||
|
||||
if ( !mMaxWidthEq.empty() ) {
|
||||
Float maxWidth( getMaxSizePx().getWidth() - mLayoutMarginPx.Left - mLayoutMarginPx.Right -
|
||||
padding.Left - padding.Right );
|
||||
if ( maxWidth > 0 && maxWidth < width )
|
||||
width = maxWidth;
|
||||
}
|
||||
|
||||
return eemax( 0.f, width );
|
||||
}
|
||||
|
||||
Float UIWidget::getMatchParentHeight() const {
|
||||
Rectf padding = Rectf();
|
||||
|
||||
if ( getParent()->isWidget() )
|
||||
padding = static_cast<UIWidget*>( getParent() )->getPadding();
|
||||
|
||||
Float height = getParent()->getPixelsSize().getHeight() - mLayoutMarginPx.Top -
|
||||
mLayoutMarginPx.Bottom - padding.Top - padding.Bottom;
|
||||
|
||||
if ( !mMaxHeightEq.empty() ) {
|
||||
Float maxHeight( getMaxSizePx().getHeight() - mLayoutMarginPx.Left - mLayoutMarginPx.Right -
|
||||
padding.Left - padding.Right );
|
||||
if ( maxHeight > 0 && maxHeight < height )
|
||||
height = maxHeight;
|
||||
}
|
||||
|
||||
return eemax( 0.f, height );
|
||||
}
|
||||
|
||||
Sizef UIWidget::getSizeFromLayoutPolicy() {
|
||||
Sizef size( getPixelsSize() );
|
||||
|
||||
if ( getLayoutWidthPolicy() == SizePolicy::MatchParent ) {
|
||||
Float w = getMatchParentWidth();
|
||||
|
||||
if ( (int)w != (int)getPixelsSize().getWidth() )
|
||||
size.setWidth( w );
|
||||
}
|
||||
|
||||
if ( getLayoutHeightPolicy() == SizePolicy::MatchParent ) {
|
||||
Float h = getMatchParentHeight();
|
||||
|
||||
if ( (int)h != (int)getPixelsSize().getHeight() )
|
||||
size.setHeight( h );
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
@@ -113,9 +113,9 @@ void UIWidgetCreator::createBaseWidgetList() {
|
||||
registeredWidget["textspan"] = UITextSpan::New;
|
||||
registeredWidget["markdownview"] = UIMarkdownView::New;
|
||||
|
||||
// Aliases
|
||||
registeredWidget["hbox"] = UILinearLayout::NewHorizontal;
|
||||
registeredWidget["vbox"] = UILinearLayout::NewVertical;
|
||||
registeredWidget["input"] = UITextInput::New;
|
||||
registeredWidget["inputpassword"] = UITextInputPassword::New;
|
||||
registeredWidget["viewpagerhorizontal"] = UIViewPager::NewHorizontal;
|
||||
registeredWidget["viewpagervertical"] = UIViewPager::NewHorizontal;
|
||||
@@ -141,20 +141,25 @@ void UIWidgetCreator::createBaseWidgetList() {
|
||||
registeredWidget["del"] = UITextSpan::NewStrikethrough;
|
||||
registeredWidget["code"] = UITextSpan::NewCode;
|
||||
registeredWidget["mark"] = UITextSpan::NewMark;
|
||||
registeredWidget["div"] = UIRichText::New;
|
||||
registeredWidget["div"] = UIRichText::NewDiv;
|
||||
registeredWidget["p"] = UIRichText::NewParagraph;
|
||||
registeredWidget["blockquote"] = [] { return UIRichText::NewWithTag( "blockquote" ); };
|
||||
registeredWidget["blockquote"] = UIRichText::NewBlockquote;
|
||||
registeredWidget["h1"] = UIRichText::NewH1;
|
||||
registeredWidget["h2"] = UIRichText::NewH2;
|
||||
registeredWidget["h3"] = UIRichText::NewH3;
|
||||
registeredWidget["h4"] = UIRichText::NewH4;
|
||||
registeredWidget["h5"] = UIRichText::NewH5;
|
||||
registeredWidget["h6"] = UIRichText::NewH6;
|
||||
registeredWidget["br"] = UIRichText::NewBr;
|
||||
registeredWidget["ul"] = UILinearLayout::NewVerticalWidthMatchParent;
|
||||
registeredWidget["ol"] = UILinearLayout::NewVerticalWidthMatchParent;
|
||||
registeredWidget["li"] = UIRichText::NewListItem;
|
||||
registeredWidget["pre"] = [] { return UIRichText::NewWithTag( "pre" ); };
|
||||
registeredWidget["pre"] = UIRichText::NewPre;
|
||||
registeredWidget["img"] = [] { return UIImage::NewWithTag( "img" ); };
|
||||
registeredWidget["input"] = UITextInput::New;
|
||||
|
||||
registeredWidget["html"] = UILinearLayout::NewVerticalWidthMatchParent;
|
||||
registeredWidget["body"] = UILinearLayout::NewVerticalWidthMatchParent;
|
||||
|
||||
sBaseListCreated = true;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
#include <eepp/ui/uimarkdownview.hpp>
|
||||
|
||||
EE_MAIN_FUNC int main( int, char** ) {
|
||||
UIApplication app( { 800, 600, "eepp - UIMarkdownView Example" } );
|
||||
|
||||
Log::instance()->setLiveWrite( true );
|
||||
Log::instance()->setLogToStdOut( true );
|
||||
|
||||
UIApplication app( { 1280, 720, "eepp - UIMarkdownView Example" } );
|
||||
|
||||
app.getUI()->loadLayoutFromString( R"xml(
|
||||
<vbox layout_width="match_parent" layout_height="match_parent">
|
||||
<ScrollView lw="mp" lh="mp">
|
||||
<MarkdownView id="markdown_view" layout_width="match_parent" layout_height="wrap_content" padding="16dp">
|
||||
<ScrollView layout_width="match_parent" layout_height="match_parent">
|
||||
<MarkdownView id="markdown_view" layout_width="match_parent" layout_height="wrap_content" padding="16dp">
|
||||
# Markdown Header 1
|
||||
## Markdown Header 2
|
||||
### Markdown Header 3
|
||||
@@ -36,9 +31,8 @@ void main() {
|
||||
printf("Hello World");
|
||||
}
|
||||
```
|
||||
</MarkdownView>
|
||||
</ScrollView>
|
||||
</vbox>
|
||||
</MarkdownView>
|
||||
</ScrollView>
|
||||
)xml" );
|
||||
|
||||
auto markdownView = app.getUI()->find<UIMarkdownView>( "markdown_view" );
|
||||
|
||||
@@ -4,24 +4,44 @@ EE_MAIN_FUNC int main( int, char** ) {
|
||||
UIApplication app( { 800, 600, "eepp - UIRichText Example" } );
|
||||
|
||||
app.getUI()->loadLayoutFromString( R"xml(
|
||||
<vbox layout_width="match_parent" layout_height="match_parent">
|
||||
<ScrollView lw="mp" lh="mp">
|
||||
<vbox layout_width="match_parent" layout_height="wrap_content" padding="8dp">
|
||||
<RichText text-selection="true" font-size="12dp"
|
||||
color="white">Welcome to the <span color="#FFD700" font-style="bold">UIRichText</span> example!
|
||||
This component supports <span color="#00FF00" font-style="italic">styled text</span>,
|
||||
<span color="#00BFFF" font-style="shadow">shadows</span>,
|
||||
and <span color="#FF4500" text-stroke-width="1dp" text-stroke-color="black">outlines</span> using <span font-family="monospace" color="#A9A9A9">HTML-like tags</span>.
|
||||
</RichText>
|
||||
<Image src="file://assets/icon/ee.png" margin="4dp" layout-gravity="center_horizontal" />
|
||||
<RichText font-size="12dp"
|
||||
color="#fefefe">We can also mix <span color="#FFD700" font-style="bold">contents</span> with more <span color="#00FF00" font-style="italic">text</span>!
|
||||
</RichText>
|
||||
</vbox>
|
||||
</ScrollView>
|
||||
</vbox>
|
||||
<ScrollView layout_width="match_parent" layout_height="match_parent">
|
||||
<vbox id="main_container" layout_width="match_parent" layout_height="wrap_content" padding="8dp">
|
||||
<RichText text-selection="true" font-size="12dp"
|
||||
color="white">Welcome to the <span color="#FFD700" font-style="bold">UIRichText</span> example!
|
||||
This component supports <span color="#00FF00" font-style="italic">styled text</span>,
|
||||
<span color="#00BFFF" font-style="shadow">shadows</span>,
|
||||
and <span color="#FF4500" text-stroke-width="1dp" text-stroke-color="black">outlines</span> using <span font-family="monospace" color="#A9A9A9">HTML-like tags</span>.
|
||||
</RichText>
|
||||
<Image src="file://assets/icon/ee.png" margin="4dp" layout-gravity="center_horizontal" />
|
||||
<RichText font-size="12dp"
|
||||
color="#fefefe">We can also mix <span color="#FFD700" font-style="bold">contents</span> with more <span color="#00FF00" font-style="italic">text</span>!
|
||||
</RichText>
|
||||
</vbox>
|
||||
</ScrollView>
|
||||
)xml" );
|
||||
|
||||
auto mainContainer = app.getUI()->find<UILinearLayout>( "main_container" );
|
||||
|
||||
app.getWindow()->getInput()->pushCallback( [mainContainer, &app]( InputEvent* event ) {
|
||||
switch ( event->Type ) {
|
||||
case InputEvent::FileDropped: {
|
||||
std::string file( event->file.file );
|
||||
std::string data;
|
||||
FileSystem::fileGet( file, data );
|
||||
mainContainer->closeAllChildren();
|
||||
app.getUI()->loadLayoutFromString( data, mainContainer );
|
||||
break;
|
||||
}
|
||||
case InputEvent::TextDropped: {
|
||||
mainContainer->closeAllChildren();
|
||||
app.getUI()->loadLayoutFromString( event->textdrop.text, mainContainer );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} );
|
||||
|
||||
app.getUI()->on( Event::KeyUp, [&app]( const Event* event ) {
|
||||
if ( event->asKeyEvent()->getKeyCode() == KEY_F11 ) {
|
||||
UIWidgetInspector::create( app.getUI() );
|
||||
|
||||
Reference in New Issue
Block a user