Skip to content

Commit

Permalink
Make the client area always tied to the padding area, textarea elemen…
Browse files Browse the repository at this point in the history
…ts now clip to their padding area

This removes the support for customizing the client area of an element, and fixes it to its padding area. This makes it easier to understand what the client area is, and is also in line with how CSS defines this term. Previously, the client area was also used to define how the element was clipped. Instead, this is now made more explicit with a separate "clip area" that can be customized for each element.

Normally, clipping occurs on the padding area of the element. The clip area is introduced mainly for single-line text inputs, as we still want to clip to the content area here.

On the other hand, textarea elements previously also clipped to the content area, but this is undesirable and not according to CSS specifications. Now, the textarea elements clip to padding, just like normal elements.

The 'invaders.rcss' textarea decorators previously assumed clipping to the content area. This would now lead to content being placed on top of the decorator when scrolling, which looks strange. Instead, span the decorator across the border-area of the element, and make a transparent border as a placeholder. This better describes the nature of the decorator, since after all it attempts to display a border. Since clipping now acts on the padding area, the content will be clipped when it is scrolled into the border area, where the decorator now displays the borders. Furthermore, adjust the position of the textarea scrollbars accordingly, and improve their looks generally.
  • Loading branch information
mikke89 committed Jul 7, 2024
1 parent 924858c commit 5858147
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 59 deletions.
34 changes: 16 additions & 18 deletions Include/RmlUi/Core/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,12 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
/// @return The absolute offset.
Vector2f GetAbsoluteOffset(BoxArea area = BoxArea::Content);

/// Sets an alternate area to use as the client area.
/// @param[in] client_area The box area to use as the element's client area.
void SetClientArea(BoxArea client_area);
/// Returns the area the element uses as its client area.
/// @return The box area used as the element's client area.
BoxArea GetClientArea() const;
/// Sets an alternate area to use as the clip area.
/// @param[in] clip_area The box area to use as the element's clip area.
void SetClipArea(BoxArea clip_area);
/// Returns the area the element uses as its clip area.
/// @return The box area used as the element's clip area.
BoxArea GetClipArea() const;

/// Sets the dimensions of the element's scrollable overflow rectangle. This is the tightest fitting box surrounding
/// all of this element's logical children, and the element's padding box.
Expand Down Expand Up @@ -364,20 +364,18 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
float GetAbsoluteTop();

/// Gets the horizontal offset from the element's left border edge to the left edge of its client area. This is
/// usually the edge of the padding, but may be the content area for some replaced elements.
/// effectively equivalent to the left border width.
/// @return The horizontal offset of the element's client area, in pixels.
float GetClientLeft();
/// Gets the vertical offset from the element's top border edge to the top edge of its client area. This is
/// usually the edge of the padding, but may be the content area for some replaced elements.
/// effectively equivalent to the top border width.
/// @return The vertical offset of the element's client area, in pixels.
float GetClientTop();
/// Gets the width of the element's client area. This is usually the padded area less the vertical scrollbar
/// width, but may be the content area for some replaced elements.
/// @return The width of the element's client area, usually including padding but not the vertical scrollbar width, border or margin.
/// Gets the width of the element's client area. This is the padding area subtracted by the vertical scrollbar width if present.
/// @return The element's client width, in pixels.
float GetClientWidth();
/// Gets the height of the element's client area. This is usually the padded area less the horizontal scrollbar
/// height, but may be the content area for some replaced elements.
/// @return The inner height of the element, usually including padding but not the horizontal scrollbar height, border or margin.
/// Gets the height of the element's client area. This is the padding area subtracted by the horizontal scrollbar height if present.
/// @return The element's client height, in pixels.
float GetClientHeight();

/// Returns the element from which all offset calculations are currently computed.
Expand All @@ -389,10 +387,10 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
/// Gets the distance from this element's top border to its offset parent's top border.
/// @return The vertical distance (in pixels) from this element's offset parent to itself.
float GetOffsetTop();
/// Gets the width of the element, including the client area, padding, borders and scrollbars, but not margins.
/// Gets the width of the element, including the content area, padding, borders and scrollbars, but not margins.
/// @return The width of the rendered element, in pixels.
float GetOffsetWidth();
/// Gets the height of the element, including the client area, padding, borders and scrollbars, but not margins.
/// Gets the height of the element, including the content area, padding, borders and scrollbars, but not margins.
/// @return The height of the rendered element, in pixels.
float GetOffsetHeight();

Expand Down Expand Up @@ -735,8 +733,8 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
OwnedElementList children;
int num_non_dom_children;

// Defines what box area represents the element's client area; this is usually padding, but may be content.
BoxArea client_area;
// Defines which box area to use for clipping; this is usually padding, but may be content.
BoxArea clip_area;

// Original tag this element came from.
String tag;
Expand Down
55 changes: 43 additions & 12 deletions Samples/assets/invader.rcss
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,17 @@ input.text:focus-visible, input.password:focus-visible

textarea
{
padding: 14dp 12dp 10dp;
decorator: ninepatch( textarea, textarea-inner, 1.0 );
padding: 5dp 8dp;
decorator: ninepatch(textarea, textarea-inner, 1.0) border-box;
cursor: text;
text-align: left;
line-height: 1.3;
border-width: 14dp 12dp 10dp;
border-color: transparent;
}
textarea:focus-visible
{
decorator: ninepatch( textarea-focus, textarea-focus-inner, 1.0 );
decorator: ninepatch(textarea-focus, textarea-focus-inner, 1.0) border-box;
}

input.text,
Expand Down Expand Up @@ -686,18 +689,33 @@ scrollbarhorizontal
textarea scrollbarvertical
{
cursor: arrow;
margin: 10dp 0 4dp 0;
width: 12dp;
margin: -3dp -7dp -3dp 7dp;
}
textarea scrollbarvertical slidertrack
{
decorator: none;
background-color: #eee;
}
textarea scrollbarvertical sliderbar
{
margin-left: 2dp;
width: 10dp;
decorator: none;
background-color: #bc0000;
border-left: 5dp #cc7272;
width: 7dp;
min-height: 16dp;
margin: 0;
}
textarea scrollbarvertical sliderbar:hover
{
decorator: none;
background-color: #ca3535;
}

textarea scrollbarvertical sliderbar:active
{
decorator: none;
background-color: #880000;
}
textarea scrollbarvertical sliderarrowdec,
textarea scrollbarvertical sliderarrowinc
Expand All @@ -709,20 +727,33 @@ textarea scrollbarvertical sliderarrowinc
textarea scrollbarhorizontal
{
cursor: arrow;
margin-left: 7dp;
height: 12dp;
height: 10dp;
margin: 3dp -7dp -3dp -3dp;
}
textarea scrollbarhorizontal sliderbar
{
background-color: #BC0000CC;
height: 8dp;
background-color: #bc0000;
border-top: 4dp #cc7272;
height: 6dp;
min-width: 10dp;
}
textarea scrollbarhorizontal sliderbar:hover
{
background-color: #B82500CC;
background-color: #ca3535;
}
textarea scrollbarhorizontal sliderbar:active
{
background-color: #770000CC;
background-color: #880000;
}
textarea scrollbarhorizontal slidertrack
{
background-color: #eee;
}

textarea scrollbarcorner
{
cursor: arrow;
background-color: #ccc;
margin-top: 3dp;
margin-left: 7dp;
}
18 changes: 9 additions & 9 deletions Source/Core/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Element::Element(const String& tag) :
owner_document = nullptr;
offset_parent = nullptr;

client_area = BoxArea::Padding;
clip_area = BoxArea::Padding;

baseline = 0.0f;

Expand Down Expand Up @@ -419,14 +419,14 @@ Vector2f Element::GetAbsoluteOffset(BoxArea area)
return absolute_offset + GetBox().GetPosition(area);
}

void Element::SetClientArea(BoxArea _client_area)
void Element::SetClipArea(BoxArea _clip_area)
{
client_area = _client_area;
clip_area = _clip_area;
}

BoxArea Element::GetClientArea() const
BoxArea Element::GetClipArea() const
{
return client_area;
return clip_area;
}

void Element::SetScrollableOverflowRectangle(Vector2f _scrollable_overflow_rectangle)
Expand Down Expand Up @@ -878,22 +878,22 @@ float Element::GetAbsoluteTop()

float Element::GetClientLeft()
{
return GetBox().GetPosition(client_area).x;
return GetBox().GetPosition(BoxArea::Padding).x;
}

float Element::GetClientTop()
{
return GetBox().GetPosition(client_area).y;
return GetBox().GetPosition(BoxArea::Padding).y;
}

float Element::GetClientWidth()
{
return GetBox().GetSize(client_area).x - meta->scroll.GetScrollbarSize(ElementScroll::VERTICAL);
return GetBox().GetSize(BoxArea::Padding).x - meta->scroll.GetScrollbarSize(ElementScroll::VERTICAL);
}

float Element::GetClientHeight()
{
return GetBox().GetSize(client_area).y - meta->scroll.GetScrollbarSize(ElementScroll::HORIZONTAL);
return GetBox().GetSize(BoxArea::Padding).y - meta->scroll.GetScrollbarSize(ElementScroll::HORIZONTAL);
}

Element* Element::GetOffsetParent()
Expand Down
10 changes: 5 additions & 5 deletions Source/Core/ElementUtilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ float ElementUtilities::GetDensityIndependentPixelRatio(Element* element)
int ElementUtilities::GetStringWidth(Element* element, const String& string, Character prior_character)
{
const auto& computed = element->GetComputedValues();
const TextShapingContext text_shaping_context{ computed.language(), computed.direction(), computed.letter_spacing() };
const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.letter_spacing()};

FontFaceHandle font_face_handle = element->GetFontFaceHandle();
if (font_face_handle == 0)
Expand Down Expand Up @@ -198,7 +198,7 @@ bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_
// Merge the existing clip region with the current clip region, unless we are ignoring clip regions.
if (((clip_always || clip_enabled) && num_ignored_clips == 0) || force_clip_current_element)
{
const BoxArea client_area = (force_clip_current_element ? BoxArea::Border : clipping_element->GetClientArea());
const BoxArea clip_area = (force_clip_current_element ? BoxArea::Border : clipping_element->GetClipArea());
const bool has_clipping_content =
(clip_always || force_clip_current_element || clipping_element->GetClientWidth() < clipping_element->GetScrollWidth() - 0.5f ||
clipping_element->GetClientHeight() < clipping_element->GetScrollHeight() - 0.5f);
Expand All @@ -215,7 +215,7 @@ bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_
// region to be clipped. If the element has a transform we only use a clip mask when the content clips.
if (has_border_radius || (transform && has_clipping_content))
{
Geometry* clip_geometry = clipping_element->GetElementBackgroundBorder()->GetClipGeometry(clipping_element, client_area);
Geometry* clip_geometry = clipping_element->GetElementBackgroundBorder()->GetClipGeometry(clipping_element, clip_area);
const ClipMaskOperation clip_operation = (out_clip_mask_list->empty() ? ClipMaskOperation::Set : ClipMaskOperation::Intersect);
const Vector2f absolute_offset = clipping_element->GetAbsoluteOffset(BoxArea::Border);
out_clip_mask_list->push_back(ClipMaskGeometry{clip_operation, clip_geometry, absolute_offset, transform});
Expand All @@ -231,8 +231,8 @@ bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_
if (has_clipping_content && !disable_scissor_clipping)
{
// Shrink the scissor region to the element's client area.
Vector2f element_offset = clipping_element->GetAbsoluteOffset(client_area);
Vector2f element_size = clipping_element->GetBox().GetSize(client_area);
Vector2f element_offset = clipping_element->GetAbsoluteOffset(clip_area);
Vector2f element_size = clipping_element->GetBox().GetSize(clip_area);

clip_region.IntersectIfValid(Rectanglef::FromPositionSize(element_offset, element_size));
}
Expand Down
40 changes: 26 additions & 14 deletions Source/Core/Elements/WidgetTextInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) :
parent->SetProperty(PropertyId::Drag, Property(Style::Drag::Drag));
parent->SetProperty(PropertyId::WordBreak, Property(Style::WordBreak::BreakWord));
parent->SetProperty(PropertyId::TextTransform, Property(Style::TextTransform::None));
parent->SetClientArea(BoxArea::Content);

parent->AddEventListener(EventId::Keydown, this, true);
parent->AddEventListener(EventId::Textinput, this, true);
Expand Down Expand Up @@ -1062,15 +1061,15 @@ int WidgetTextInput::CalculateLineIndex(float position) const

float WidgetTextInput::GetAlignmentSpecificTextOffset(const char* p_begin, int line_index) const
{
const float client_width = parent->GetClientWidth();
const float available_width = GetAvailableWidth();
const float total_width = (float)ElementUtilities::GetStringWidth(text_element, String(p_begin, lines[line_index].editable_length));
auto text_align = GetElement()->GetComputedValues().text_align();

// offset position depending on text align
switch (text_align)
{
case Style::TextAlign::Right: return Math::Max(0.0f, (client_width - total_width));
case Style::TextAlign::Center: return Math::Max(0.0f, ((client_width - total_width) / 2));
case Style::TextAlign::Right: return Math::Max(0.0f, (available_width - total_width));
case Style::TextAlign::Center: return Math::Max(0.0f, ((available_width - total_width) / 2));
default: break;
}

Expand Down Expand Up @@ -1125,13 +1124,13 @@ void WidgetTextInput::ShowCursor(bool show, bool move_to_cursor)
// Shift the cursor into view.
if (move_to_cursor)
{
float minimum_scroll_top = (cursor_position.y + cursor_size.y) - parent->GetClientHeight();
float minimum_scroll_top = (cursor_position.y + cursor_size.y) - GetAvailableHeight();
if (parent->GetScrollTop() < minimum_scroll_top)
parent->SetScrollTop(minimum_scroll_top);
else if (parent->GetScrollTop() > cursor_position.y)
parent->SetScrollTop(cursor_position.y);

float minimum_scroll_left = (cursor_position.x + cursor_size.x) - parent->GetClientWidth();
float minimum_scroll_left = (cursor_position.x + cursor_size.x) - GetAvailableWidth();
if (parent->GetScrollLeft() < minimum_scroll_left)
parent->SetScrollLeft(minimum_scroll_left);
else if (parent->GetScrollLeft() > cursor_position.x)
Expand Down Expand Up @@ -1178,26 +1177,28 @@ void WidgetTextInput::FormatElement()
scroll->DisableScrollbar(ElementScroll::VERTICAL);

// If the formatting produces scrollbars we need to format again later, this constraint enables early exit for the first formatting round.
const float formatting_height_constraint = (y_overflow_property == Overflow::Auto ? parent->GetClientHeight() : FLT_MAX);
const float formatting_height_constraint = (y_overflow_property == Overflow::Auto ? GetAvailableHeight() : FLT_MAX);

// Format the text and determine its total area.
Vector2f content_area = FormatText(formatting_height_constraint);

// If we're set to automatically generate horizontal scrollbars, check for that now.
if (!word_wrap && x_overflow_property == Overflow::Auto && content_area.x > parent->GetClientWidth() + OVERFLOW_TOLERANCE)
if (!word_wrap && x_overflow_property == Overflow::Auto && content_area.x > GetAvailableWidth() + OVERFLOW_TOLERANCE)
scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width);

// Now check for vertical overflow. If we do turn on the scrollbar, this will cause a reflow.
if (y_overflow_property == Overflow::Auto && content_area.y > parent->GetClientHeight() + OVERFLOW_TOLERANCE)
if (y_overflow_property == Overflow::Auto && content_area.y > GetAvailableHeight() + OVERFLOW_TOLERANCE)
{
scroll->EnableScrollbar(ElementScroll::VERTICAL, width);
content_area = FormatText();

if (!word_wrap && x_overflow_property == Overflow::Auto && content_area.x > parent->GetClientWidth() + OVERFLOW_TOLERANCE)
if (!word_wrap && x_overflow_property == Overflow::Auto && content_area.x > GetAvailableWidth() + OVERFLOW_TOLERANCE)
scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width);
}

parent->SetScrollableOverflowRectangle(content_area);
// For text elements, make the content and padding on all sides reachable by scrolling.
const Vector2f padding_size = parent->GetBox().GetFrameSize(BoxArea::Padding);
parent->SetScrollableOverflowRectangle(content_area + padding_size);
scroll->FormatScrollbars();
}

Expand Down Expand Up @@ -1225,7 +1226,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
// When the selection contains endlines, we expand the selection area by this width.
const int endline_font_width = int(0.4f * parent->GetComputedValues().font_size());

const float client_width = parent->GetClientWidth();
const float available_width = GetAvailableWidth();
int line_begin = 0;
Vector2f line_position = {0, top_to_baseline};
bool last_line = false;
Expand Down Expand Up @@ -1253,7 +1254,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
// Keep generating lines until all the text content is placed.
do
{
if (client_width <= 0.f)
if (available_width <= 0.f)
{
lines.push_back(Line{});
break;
Expand All @@ -1265,7 +1266,8 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
String line_content;

// Generate the next line.
last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, client_width - cursor_size.x, 0, false, false, false);
last_line =
text_element->GenerateLine(line_content, line.size, line_width, line_begin, available_width - cursor_size.x, 0, false, false, false);

// If this line terminates in a soft-return (word wrap), then the line may be leaving a space or two behind as an orphan. If so, we must
// append the orphan onto the line even though it will push the line outside of the input field's bounds.
Expand Down Expand Up @@ -1606,4 +1608,14 @@ void WidgetTextInput::SetKeyboardActive(bool active)
}
}

float WidgetTextInput::GetAvailableWidth() const
{
return parent->GetClientWidth() - parent->GetBox().GetFrameSize(BoxArea::Padding).x;
}

float WidgetTextInput::GetAvailableHeight() const
{
return parent->GetClientHeight() - parent->GetBox().GetFrameSize(BoxArea::Padding).y;
}

} // namespace Rml
5 changes: 5 additions & 0 deletions Source/Core/Elements/WidgetTextInput.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ class WidgetTextInput : public EventListener {
/// @param[in] line_begin The absolute index at the beginning of the line.
void GetLineIMEComposition(String& pre_composition, String& ime_composition, const String& line, int line_begin) const;

/// Returns the width available for the text contents without overflowing, that is, the content area subtracted by any scrollbar.
float GetAvailableWidth() const;
/// Returns the height available for the text contents without overflowing, that is, the content area subtracted by any scrollbar.
float GetAvailableHeight() const;

struct Line {
// Offset into the text field's value.
int value_offset;
Expand Down
Loading

0 comments on commit 5858147

Please sign in to comment.