Ver Fonte

ElementHandle: Keep the target anchored after being positioned and sized, resolves #637

This commit changes the how the position and size of handle targets are determined. If an element has all of its inset (top/right/bottom/left) properties set, this determines the size. Previously, we converted this to a width/height and always used top/left when sizing and positioning the target element. This is no longer the case, we now keep the target anchored to the same sides as it was anchored to before positioning and sizing.

Now, when first sizing and moving the target, and then resizing its container, the element can now still size itself to match the new dimensions.

This commit additionally fixes a couple issues, where the element jolts some distance at drag start:

1. When the target's offset parent had a border.
2. When set to relative positioning and placed away from the top-left corner.
Michael Ragazzon há 1 ano atrás
pai
commit
570bd1632a

+ 91 - 25
Source/Core/ElementHandle.cpp

@@ -28,6 +28,7 @@
 
 #include "ElementHandle.h"
 #include "../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../Include/RmlUi/Core/Context.h"
 #include "../../Include/RmlUi/Core/ElementDocument.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/Event.h"
@@ -80,10 +81,6 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 			initialised = true;
 		}
 
-		auto GetSize = [](const Box& box, const ComputedValues& computed) {
-			return box.GetSize((computed.box_sizing() == Style::BoxSizing::BorderBox) ? BoxArea::Border : BoxArea::Content);
-		};
-
 		// Set any auto margins to their current value, since auto-margins may affect the size and position of an element.
 		auto SetDefiniteMargins = [](Element* element, const ComputedValues& computed) {
 			auto SetDefiniteMargin = [](Element* element, PropertyId margin_id, BoxEdge edge) {
@@ -100,39 +97,90 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 				SetDefiniteMargin(element, PropertyId::MarginLeft, BoxEdge::Left);
 		};
 
+		// The following table lists the expected behavior for each combination of definite (non-auto) properties:
+		//
+		//   Definite properties  | Move                    | Size
+		//   ---------------------|-------------------------|--------------------------
+		//   (none)               | left += dx              | width += dx
+		//   left                 | left += dx              | width += dx
+		//   right                | right -= dx             | right -= dx; width += dx
+		//   width                | left += dx              | width += dx
+		//   left & right         | left += dx; right -= dx | right -= dx
+		//   left & width         | left += dx;             | width += dx
+		//   right & width        | right -= dx             | right -= dx; width += dx
+		//   right & width        | right -= dx             | right -= dx; width += dx
+		//   left & right & width | left += dx;             | width += dx
+		//
+		// For simplicity, this table only specifies the horizontal direction. The same behavior applies for the
+		// corresponding properties in the vertical direction. For now, we assume that the handle is anchored to the
+		// bottom-right corner of the target element.
+
 		if (event == EventId::Dragstart)
 		{
-			// Store the drag starting position
+			Context* context = GetContext();
 			drag_start = event.GetUnprojectedMouseScreenPos();
 
-			// Store current element position and size
-			if (move_target)
+			if (move_target && context)
 			{
 				using namespace Style;
+				const Box& parent_box =
+					move_target->GetOffsetParent() ? move_target->GetOffsetParent()->GetBox() : context->GetRootElement()->GetBox();
+				const Vector2f containing_block = move_target->GetContainingBlock();
 				const Box& box = move_target->GetBox();
 				const auto& computed = move_target->GetComputedValues();
-
-				// Store the initial margin edge position, since top/left properties determine the margin position.
-				move_original_position.x = move_target->GetOffsetLeft() - box.GetEdge(BoxArea::Margin, BoxEdge::Left);
-				move_original_position.y = move_target->GetOffsetTop() - box.GetEdge(BoxArea::Margin, BoxEdge::Top);
-
-				// Check if we have auto-size together with definite right/bottom; if so, the size needs to be fixed to the current size.
-				if (computed.width().type == Width::Auto && computed.right().type != Right::Auto)
-					move_target->SetProperty(PropertyId::Width, Property(Math::Round(GetSize(box, computed).x), Unit::PX));
-				if (computed.height().type == Height::Auto && computed.bottom().type != Bottom::Auto)
-					move_target->SetProperty(PropertyId::Height, Property(Math::Round(GetSize(box, computed).y), Unit::PX));
+				const Position position = computed.position();
 
 				SetDefiniteMargins(move_target, computed);
+
+				auto ResolveValueOrInvoke = [](const LengthPercentageAuto& value, float containing_block, Position position, auto&& fallback_func) {
+					if (value.type != LengthPercentageAuto::Auto)
+						return ResolveValue(value, containing_block);
+					if (position == Position::Relative)
+						return 0.0f;
+					return fallback_func();
+				};
+
+				// The following is derived at by solving the expressions in 'Element::UpdateOffset' for the computed top/left values.
+				move_original_position_top_left.x = ResolveValueOrInvoke(computed.left(), containing_block.x, position, [&] {
+					return move_target->GetOffsetLeft() -
+						(box.GetEdge(BoxArea::Margin, BoxEdge::Left) + parent_box.GetEdge(BoxArea::Border, BoxEdge::Left));
+				});
+				move_original_position_top_left.y = ResolveValueOrInvoke(computed.top(), containing_block.y, position, [&] {
+					return move_target->GetOffsetTop() -
+						(box.GetEdge(BoxArea::Margin, BoxEdge::Top) + parent_box.GetEdge(BoxArea::Border, BoxEdge::Top));
+				});
+
+				// For the bottom-right positions, we don't need to find the 'auto'-invoked expressions like above,
+				// since the following value is only used when bottom-right is non-auto.
+				move_original_position_bottom_right.x = ResolveValue(computed.right(), containing_block.x);
+				move_original_position_bottom_right.y = ResolveValue(computed.bottom(), containing_block.y);
+
+				move_bottom_right.x = (computed.right().type != Right::Auto);
+				move_bottom_right.y = (computed.bottom().type != Bottom::Auto);
+				move_top_left.x = (!move_bottom_right.x || computed.left().type != Left::Auto);
+				move_top_left.y = (!move_bottom_right.y || computed.top().type != Top::Auto);
 			}
-			if (size_target)
+
+			if (size_target && context)
 			{
 				using namespace Style;
 				const Box& box = size_target->GetBox();
 				const auto& computed = size_target->GetComputedValues();
-
-				size_original_size = GetSize(box, computed);
+				const Vector2f containing_block = size_target->GetContainingBlock();
 
 				SetDefiniteMargins(size_target, computed);
+
+				size_original_size = box.GetSize(computed.box_sizing() == BoxSizing::BorderBox ? BoxArea::Border : BoxArea::Content);
+
+				size_original_position_bottom_right.x = ResolveValue(computed.right(), containing_block.x);
+				size_original_position_bottom_right.y = ResolveValue(computed.bottom(), containing_block.y);
+
+				size_bottom_right.x = (computed.right().type != Right::Auto);
+				size_bottom_right.y = (computed.bottom().type != Bottom::Auto);
+				size_width_height.x =
+					(computed.left().type == Left::Auto || computed.right().type == Right::Auto || computed.width().type != Width::Auto);
+				size_width_height.y =
+					(computed.top().type == Top::Auto || computed.bottom().type == Bottom::Auto || computed.height().type != Height::Auto);
 			}
 		}
 		else if (event == EventId::Drag)
@@ -141,16 +189,34 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 
 			if (move_target)
 			{
-				const Vector2f new_position = (move_original_position + delta).Round();
-				move_target->SetProperty(PropertyId::Left, Property(new_position.x, Unit::PX));
-				move_target->SetProperty(PropertyId::Top, Property(new_position.y, Unit::PX));
+				const Vector2f new_position_top_left = (move_original_position_top_left + delta).Round();
+				const Vector2f new_position_bottom_right = (move_original_position_bottom_right - delta).Round();
+
+				if (move_top_left.x)
+					move_target->SetProperty(PropertyId::Left, Property(new_position_top_left.x, Unit::PX));
+				if (move_top_left.y)
+					move_target->SetProperty(PropertyId::Top, Property(new_position_top_left.y, Unit::PX));
+
+				if (move_bottom_right.x)
+					move_target->SetProperty(PropertyId::Right, Property(new_position_bottom_right.x, Unit::PX));
+				if (move_bottom_right.y)
+					move_target->SetProperty(PropertyId::Bottom, Property(new_position_bottom_right.y, Unit::PX));
 			}
 
 			if (size_target)
 			{
 				const Vector2f new_size = Math::Max((size_original_size + delta).Round(), Vector2f(0.f));
-				size_target->SetProperty(PropertyId::Width, Property(new_size.x, Unit::PX));
-				size_target->SetProperty(PropertyId::Height, Property(new_size.y, Unit::PX));
+				const Vector2f new_position_bottom_right = (size_original_position_bottom_right - delta).Round();
+
+				if (size_width_height.x)
+					size_target->SetProperty(PropertyId::Width, Property(new_size.x, Unit::PX));
+				if (size_width_height.y)
+					size_target->SetProperty(PropertyId::Height, Property(new_size.y, Unit::PX));
+
+				if (size_bottom_right.x)
+					size_target->SetProperty(PropertyId::Right, Property(new_position_bottom_right.x, Unit::PX));
+				if (size_bottom_right.y)
+					size_target->SetProperty(PropertyId::Bottom, Property(new_position_bottom_right.y, Unit::PX));
 			}
 
 			Dictionary parameters;

+ 14 - 7
Source/Core/ElementHandle.h

@@ -35,9 +35,8 @@
 namespace Rml {
 
 /**
-    A derivation of an element for use as a mouse handle. A handle is designed to be instanced and attached as a non-
-    DOM element to a window-style element, and listened to for movement events which can be responsed to to move or
-    resize the element as appropriate.
+    A derivation of an element for use as a mouse drag handle. It responds to drag events, and can be configured to move
+    or resize specified target elements.
 
     @author Peter Curry
  */
@@ -53,14 +52,22 @@ protected:
 	void OnAttributeChange(const ElementAttributes& changed_attributes) override;
 	void ProcessDefaultAction(Event& event) override;
 
-	Vector2f drag_start;
-	Vector2f move_original_position;
-	Vector2f size_original_size;
+	bool initialised;
 
 	Element* move_target;
 	Element* size_target;
 
-	bool initialised;
+	Vector2f drag_start;
+
+	Vector2f move_original_position_top_left;
+	Vector2f move_original_position_bottom_right;
+	Vector2<bool> move_top_left;
+	Vector2<bool> move_bottom_right;
+
+	Vector2f size_original_size;
+	Vector2f size_original_position_bottom_right;
+	Vector2<bool> size_width_height;
+	Vector2<bool> size_bottom_right;
 };
 
 } // namespace Rml

+ 89 - 0
Tests/Data/VisualTests/handle_target_anchor.rml

@@ -0,0 +1,89 @@
+<rml>
+<head>
+	<title>Handle anchor positioning</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="Handles should respect the target element's anchoring to its parent, determined by its top/right/bottom/left properties. The anchoring should still hold after moving and resizing the target." />
+	<style>
+		handle[move_target] {
+			top: 0;
+			right: 0;
+			left: 0;
+			bottom: auto;
+			height: 20px;
+			width: auto;
+			cursor: move;
+		}
+		body, div {
+			border: 5dp #fff;
+			padding-top: 2em;
+		}
+		body {
+			text-align: center;
+			z-index: 100;
+			box-sizing: border-box;
+			border: 5dp #222;
+			width: auto;
+			height: auto;
+			top: 100dp;
+			bottom: 100dp;
+			left: 100dp;
+			right: 580dp;
+		}
+		div {
+			position: absolute;
+			z-index: 1;
+			background: #eee;
+			min-width: 100dp;
+			min-height: 50dp;
+			border-width: 0;
+			margin: 0;
+		}
+		.top_left {
+			top: 25px;
+			left: 25px;
+			width: 180dp;
+			height: 100dp;
+		}
+		.top_right_bottom_left {
+			top: 200px;
+			left: 250px;
+			bottom: 200px;
+			right: 250px;
+		}
+		.bottom_right {
+			bottom: 25px;
+			right: 25px;
+			width: 180dp;
+			height: 100dp;
+		}
+	</style>
+</head>
+
+<body>
+<p>Document</p>
+
+<div class="top_left">
+	<p>Anchored top-left</p>
+
+	<handle move_target="#parent"/>
+	<handle size_target="#parent"/>
+</div>
+
+<div class="top_right_bottom_left">
+	<p>Anchored all sides</p>
+
+	<handle move_target="#parent"/>
+	<handle size_target="#parent"/>
+</div>
+
+<div class="bottom_right">
+	<p>Anchored bottom-right</p>
+
+	<handle move_target="#parent"/>
+	<handle size_target="#parent"/>
+</div>
+
+<handle move_target="#parent"/>
+<handle size_target="#parent"/>
+</body>
+</rml>