Browse Source

Using client regions instead of plain parent size to update layout, so that decorations can be added around child components.

David Piuva 2 years ago
parent
commit
9b2b8926c1

+ 0 - 1
Source/DFPSR/api/guiAPI.cpp

@@ -91,7 +91,6 @@ Component dsr::component_createWithInterfaceFromString(Component& parent, const
 		throwError(U"component_createWithInterfaceFromString: The component could not be created!\n\nLayout:\n", content, "\n");
 	}
 	parent->addChildComponent(result);
-	result->applyLayout(parent->getSize());
 	return result;
 }
 

+ 1 - 1
Source/DFPSR/gui/DsrWindow.cpp

@@ -89,7 +89,7 @@ DsrWindow::DsrWindow(std::shared_ptr<BackendWindow> backend)
 DsrWindow::~DsrWindow() {}
 
 void DsrWindow::applyLayout() {
-	this->mainPanel->applyLayout(IVector2D(this->getCanvasWidth(), this->getCanvasHeight()));
+	this->mainPanel->applyLayout(IRect(0, 0, this->getCanvasWidth(), this->getCanvasHeight()));
 }
 
 std::shared_ptr<VisualComponent> DsrWindow::findComponentByName(ReadableString name) const {

+ 5 - 5
Source/DFPSR/gui/FlexRegion.cpp

@@ -57,12 +57,12 @@ String& FlexValue::toStreamIndented(String& out, const ReadableString& indentati
 	return out;
 }
 
-IRect FlexRegion::getNewLocation(const IVector2D &parentSize) {
+IRect FlexRegion::getNewLocation(const IRect &givenSpace) {
 	return IRect::FromBounds(
-		this->sides[0].getValue(parentSize.x),
-		this->sides[1].getValue(parentSize.y),
-		this->sides[2].getValue(parentSize.x),
-		this->sides[3].getValue(parentSize.y)
+		this->sides[0].getValue(givenSpace.left(), givenSpace.right()),
+		this->sides[1].getValue(givenSpace.top(), givenSpace.bottom()),
+		this->sides[2].getValue(givenSpace.left(), givenSpace.right()),
+		this->sides[3].getValue(givenSpace.top(), givenSpace.bottom())
 	);
 }
 

+ 2 - 2
Source/DFPSR/gui/FlexRegion.h

@@ -46,7 +46,7 @@ public:
 public:
 	int32_t getRatio() const { return this->ratio; }
 	int32_t getOffset() const { return this->offset; }
-	int32_t getValue(int32_t parentValue) const { return (parentValue * this->ratio) / 100 + this->offset; }
+	int32_t getValue(int32_t minimum, int32_t maximum) const { return ((minimum * (100 - this->ratio)) + (maximum * this->ratio)) / 100 + this->offset; }
 };
 inline bool operator==(const FlexValue &left, const FlexValue &right) {
 	return left.getRatio() == right.getRatio() && left.getOffset() == right.getOffset();
@@ -98,7 +98,7 @@ public:
 		this->setBottom(bottom);
 	}
 public:
-	virtual IRect getNewLocation(const IVector2D &parentSize);
+	virtual IRect getNewLocation(const IRect &givenSpace);
 };
 
 }

+ 24 - 9
Source/DFPSR/gui/VisualComponent.cpp

@@ -39,6 +39,11 @@ VisualComponent::~VisualComponent() {
 	}
 }
 
+IVector2D VisualComponent::getDesiredDimensions() {
+	// Unless this virtual method is overridden, toolbars and such will try to give these dimensions to the component.
+	return IVector2D(32, 32);
+}
+
 bool VisualComponent::isContainer() const {
 	return true;
 }
@@ -53,10 +58,6 @@ IRect VisualComponent::getLocation() {
 	return this->location;
 }
 
-IVector2D VisualComponent::getSize() {
-	return this->getLocation().size();
-}
-
 void VisualComponent::setRegion(const FlexRegion &newRegion) {
 	this->region = newRegion;
 }
@@ -99,24 +100,34 @@ void VisualComponent::setLocation(const IRect &newLocation) {
 }
 
 void VisualComponent::updateLayout() {
-	this->setLocation(this->region.getNewLocation(this->parentSize));
+	this->setLocation(this->region.getNewLocation(this->givenSpace));
 }
 
-void VisualComponent::applyLayout(IVector2D parentSize) {
-	this->parentSize = parentSize;
+void VisualComponent::applyLayout(const IRect& givenSpace) {
+	this->givenSpace = givenSpace;
 	this->updateLayout();
 }
 
 void VisualComponent::updateLocationEvent(const IRect& oldLocation, const IRect& newLocation) {
 	// Place each child component
 	for (int i = 0; i < this->getChildCount(); i++) {
-		this->children[i]->applyLayout(newLocation.size());
+		this->children[i]->applyLayout(IRect(0, 0, newLocation.width(), newLocation.height()));
+	}
+}
+
+// Check if any change requires the child layout to update.
+//   Used to realign members of toolbars after a desired dimension changed.
+void VisualComponent::updateChildLocations() {
+	if (this->childChanged) {
+		this->updateLocationEvent(this->location, this->location);
+		this->childChanged = false;
 	}
 }
 
 // Offset may become non-zero when the origin is outside of targetImage from being clipped outside of the parent region
 void VisualComponent::draw(ImageRgbaU8& targetImage, const IVector2D& offset) {
 	if (this->getVisible()) {
+		this->updateChildLocations();
 		IRect containerBound = this->getLocation() + offset;
 		this->drawSelf(targetImage, containerBound);
 		// Draw each child component
@@ -153,9 +164,10 @@ void VisualComponent::addChildComponent(std::shared_ptr<VisualComponent> child)
 		// Remove from any previous parent
 		child->detachFromParent();
 		// Update layout based on the new parent size
-		child->applyLayout(this->getSize());
+		child->applyLayout(IRect(0, 0, this->location.width(), this->location.height()));
 		// Connect to the new parent
 		this->children.push(child);
+		this->childChanged = true;
 		child->parent = this;
 	}
 }
@@ -188,6 +200,7 @@ void VisualComponent::detachFromParent() {
 	// Check if there's a parent component
 	VisualComponent *parent = this->parent;
 	if (parent != nullptr) {
+		parent->childChanged = true;
 		// If the removed component is focused from the parent, then remove focus so that the parent is focused instead.
 		if (parent->focusComponent.get() == this) {
 			parent->focusComponent = std::shared_ptr<VisualComponent>();
@@ -292,6 +305,8 @@ std::shared_ptr<VisualComponent> VisualComponent::getTopChild(const IVector2D& p
 }
 
 void VisualComponent::sendMouseEvent(const MouseEvent& event) {
+	// Update the layout if needed
+	this->updateChildLocations();
 	// Convert to local coordinates recursively
 	MouseEvent localEvent = event - this->getLocation().upperLeft();
 	std::shared_ptr<VisualComponent> childComponent;

+ 15 - 7
Source/DFPSR/gui/VisualComponent.h

@@ -42,7 +42,7 @@ PERSISTENT_DECLARATION(VisualComponent)
 protected:
 	// Parent component
 	VisualComponent *parent = nullptr;
-	IVector2D parentSize; // Remembering the parent's size so that the root component can remember the window's size when moving
+	IRect givenSpace; // Remembering the local region that was reserved inside of the parent component
 	bool regionAccessed = false; // If someone requested access to the region, remember to update layout in case of new settings
 	// Child components
 	List<std::shared_ptr<VisualComponent>> children;
@@ -107,7 +107,6 @@ public:
 public:
 	virtual bool isContainer() const;
 	IRect getLocation();
-	IVector2D getSize();
 	void setRegion(const FlexRegion &newRegion);
 	FlexRegion getRegion() const;
 	void setVisible(bool visible);
@@ -182,15 +181,24 @@ public:
 	// Detach the component from any parent
 	void detachFromParent();
 
-	// Adapt the location based on the region
-	//   parentWidth must be the current width of the parent container
-	//   parentHeight must be the current height of the parent container
-	// Override to apply a custom behaviour, which may be useful for fixed size components.
-	virtual void applyLayout(IVector2D parentSize);
+	// Adapt the location based on the space given by the parent.
+	//   The given space is usually a rectangle starting at the origin with the same dimensions as the parent component.
+	//   If the parent has decorations around the child components, the region may include some padding from which the flexible regions calculate the locations from in percents.
+	//     For example: A given space from 10 to 90 pixels will have 0% at 10 and 100% at 90.
+	//   A toolbar may give non-overlapping spaces that are assigned automatically to simplify the process of maintaining the layout while adding and removing child components.
+	// TODO: How can internal changes to inner dimensions be detected by the parent to run this method again? Call the parent and tell it to update next time something needs drawing or mouse input?
+	virtual void applyLayout(const IRect& givenSpace);
 	// Update layout when the component moved but the parent has the same dimensions
 	void updateLayout();
+	// Parent components that place child components automatically can ask them what their minimum useful dimensions are in pixels, so that their text will be visible.
+	// The component can still be resized to less than these dimensions, because the outer components can't give more space than what is given by the window.
+	virtual IVector2D getDesiredDimensions();
 	// Called after the component has been created, moved or resized.
 	virtual void updateLocationEvent(const IRect& oldLocation, const IRect& newLocation);
+	// Calling updateLocationEvent without changing the location, to be used when a child component changed its desired dimensions from altering attributes.
+	bool childChanged = false;
+	// Called before rendering or getting mouse input in case that a child component changed desired dimensions.
+	void updateChildLocations();
 	// Returns true iff the pixel with its upper left corner at pixelPosition is inside the component.
 	// A rectangular bound check with location is used by default.
 	// The caller is responsible for checking if the component is visible when needed.