diff options
author | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-08-16 17:32:51 +0200 |
---|---|---|
committer | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-10-17 20:38:16 +0200 |
commit | 7823c15c4dd68faa911d3807c8133f358854eb3e (patch) | |
tree | f3f937b5b5880b91f4597a00f43924cea9f8a8ec | |
parent | a4301a9954d4835a4b87344b9b037a816b17286d (diff) |
Add support for aspect ratio in Widget
Images should preserve their own aspect ratio, which is determined by
the image buffer size, also known as the intrinsic size. Currently, when
a child requests a requisition to be corrected by correctRequisition(),
only the size was adjusted, ignoring the aspect ratio.
The new Widget ratio variable holds the desired aspect ratio for the
widget, and will only be taken into account when non-zero. In that case,
then correcting the requisition, a naive algorithm tries to first
increase the length of the small size to fill the preferred aspect
ratio. In the case that it is not enough, the larger size is then
decreased to fit the aspect ratio. And if that doesn't work either, the
aspect ratio is not enforced and the widget will be distorted.
There are special cases for correctRequisition() depending if the parent
exists or not. The same approach is taken for both cases, but using the
viewport size instead of the parent size.
-rw-r--r-- | dw/image.cc | 19 | ||||
-rw-r--r-- | dw/image.hh | 1 | ||||
-rw-r--r-- | dw/widget.cc | 230 | ||||
-rw-r--r-- | dw/widget.hh | 14 |
4 files changed, 196 insertions, 68 deletions
diff --git a/dw/image.cc b/dw/image.cc index 603f2964..9adf17f1 100644 --- a/dw/image.cc +++ b/dw/image.cc @@ -402,22 +402,6 @@ void Image::sizeRequestSimpl (core::Requisition *requisition) DBG_OBJ_LEAVE (); } -void Image::setReqWidth(core::Requisition *requisition, int width) -{ - /* If we have the image buffer, try to set the height to preserve the image - * ratio */ - if (buffer) { - int w = buffer->getRootWidth(); - int h = buffer->getRootHeight(); - float ratio = (float) h / (float) w; - int height = (float) width * ratio; - /* Preserve descent */ - requisition->ascent = height - requisition->descent; - } - - requisition->width = width; -} - void Image::getExtremesSimpl (core::Extremes *extremes) { int contentWidth; @@ -682,6 +666,9 @@ void Image::setBuffer (core::Imgbuf *buffer, bool resize) } queueResize (0, true); + if (bufWidth) + this->ratio = (float) bufWidth / (float) bufHeight; + DBG_OBJ_ASSOC_CHILD (this->buffer); DBG_OBJ_SET_NUM ("bufWidth", bufWidth); DBG_OBJ_SET_NUM ("bufHeight", bufHeight); diff --git a/dw/image.hh b/dw/image.hh index 5d890c4e..ae47f307 100644 --- a/dw/image.hh +++ b/dw/image.hh @@ -130,7 +130,6 @@ private: bool isMap; protected: - void setReqWidth(core::Requisition *requisition, int width); void sizeRequestSimpl (core::Requisition *requisition); void getExtremesSimpl (core::Extremes *extremes); void sizeAllocateImpl (core::Allocation *allocation); diff --git a/dw/widget.cc b/dw/widget.cc index d40eed97..ef4d6b46 100644 --- a/dw/widget.cc +++ b/dw/widget.cc @@ -18,6 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +//#define DEBUG_LEVEL 1 #include "core.hh" #include "../lout/msg.h" @@ -30,6 +31,15 @@ using namespace lout::misc; namespace dw { namespace core { +/* Used to determine which action to take when correcting the aspect ratio of a + * requisition in Widget::correctReqAspectRatio(). */ +enum { + PASS_INCREASE = 0, + PASS_DECREASE = 1, + PASS_KEEP = 2 +}; + + // ---------------------------------------------------------------------- bool Widget::WidgetImgRenderer::readyToDraw () @@ -99,6 +109,8 @@ Widget::Widget () widgetImgRenderer = NULL; stackingContextMgr = NULL; + + ratio = 0.0; } Widget::~Widget () @@ -746,6 +758,57 @@ int Widget::getAvailHeight (bool forceValue) return height; } +/** + * Corrects a requisition to fit in the viewport. + * + * Instead of asking the parent widget, it uses the viewport dimensions to + * correct the requisition if needed. This is used for the top level widget (the + * body) which doesn't have any parent. + */ +void Widget::correctRequisitionViewport (Requisition *requisition, + void (*splitHeightFun) (int, int *, int *), + bool allowDecreaseWidth, + bool allowDecreaseHeight) +{ + int limitMinWidth = getMinWidth (NULL, true); + if (!allowDecreaseWidth && limitMinWidth < requisition->width) + limitMinWidth = requisition->width; + + int viewportWidth = + layout->viewportWidth - (layout->canvasHeightGreater ? + layout->vScrollbarThickness : 0); + calcFinalWidth (getStyle (), viewportWidth, NULL, limitMinWidth, false, + &requisition->width); + + // For layout->viewportHeight, see comment in getAvailHeight(). + int height = calcHeight (getStyle()->height, false, + layout->viewportHeight, NULL, false); + adjustHeight (&height, allowDecreaseHeight, requisition->ascent, + requisition->descent); + + int minHeight = calcHeight (getStyle()->minHeight, false, + layout->viewportHeight, NULL, false); + adjustHeight (&minHeight, allowDecreaseHeight, requisition->ascent, + requisition->descent); + + int maxHeight = calcHeight (getStyle()->maxHeight, false, + layout->viewportHeight, NULL, false); + adjustHeight (&maxHeight, allowDecreaseHeight, requisition->ascent, + requisition->descent); + + // TODO Perhaps split first, then add box ascent and descent. + if (height != -1) + splitHeightFun (height, &requisition->ascent, &requisition->descent); + if (minHeight != -1 && + requisition->ascent + requisition->descent < minHeight) + splitHeightFun (minHeight, &requisition->ascent, + &requisition->descent); + if (maxHeight != -1 && + requisition->ascent + requisition->descent > maxHeight) + splitHeightFun (maxHeight, &requisition->ascent, + &requisition->descent); +} + void Widget::correctRequisition (Requisition *requisition, void (*splitHeightFun) (int, int *, int *), bool allowDecreaseWidth, @@ -763,43 +826,16 @@ void Widget::correctRequisition (Requisition *requisition, DBG_OBJ_MSG ("resize", 1, "no parent, regarding viewport"); DBG_OBJ_MSG_START (); - int limitMinWidth = getMinWidth (NULL, true); - if (!allowDecreaseWidth && limitMinWidth < requisition->width) - limitMinWidth = requisition->width; - - int viewportWidth = - layout->viewportWidth - (layout->canvasHeightGreater ? - layout->vScrollbarThickness : 0); - calcFinalWidth (getStyle (), viewportWidth, NULL, limitMinWidth, false, - &requisition->width); + for (int pass = 0; pass < 3; pass++) { + correctRequisitionViewport (requisition, splitHeightFun, + allowDecreaseWidth, allowDecreaseHeight); + bool changed = correctReqAspectRatio (pass, this, requisition, + allowDecreaseWidth, allowDecreaseHeight, splitHeightFun); - // For layout->viewportHeight, see comment in getAvailHeight(). - int height = calcHeight (getStyle()->height, false, - layout->viewportHeight, NULL, false); - adjustHeight (&height, allowDecreaseHeight, requisition->ascent, - requisition->descent); - - int minHeight = calcHeight (getStyle()->minHeight, false, - layout->viewportHeight, NULL, false); - adjustHeight (&minHeight, allowDecreaseHeight, requisition->ascent, - requisition->descent); - - int maxHeight = calcHeight (getStyle()->maxHeight, false, - layout->viewportHeight, NULL, false); - adjustHeight (&maxHeight, allowDecreaseHeight, requisition->ascent, - requisition->descent); - - // TODO Perhaps split first, then add box ascent and descent. - if (height != -1) - splitHeightFun (height, &requisition->ascent, &requisition->descent); - if (minHeight != -1 && - requisition->ascent + requisition->descent < minHeight) - splitHeightFun (minHeight, &requisition->ascent, - &requisition->descent); - if (maxHeight != -1 && - requisition->ascent + requisition->descent > maxHeight) - splitHeightFun (maxHeight, &requisition->ascent, - &requisition->descent); + /* No need to repeat more passes */ + if (!changed) + break; + } DBG_OBJ_MSG_END (); } else if (parent) { @@ -1859,9 +1895,18 @@ void Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition, (child->container ? child->container : child->parent); if (effContainer == this) { - correctReqWidthOfChild (child, requisition, allowDecreaseWidth); - correctReqHeightOfChild (child, requisition, splitHeightFun, - allowDecreaseHeight); + /* Try several passes before giving up */ + for (int pass = 0; pass < 3; pass++) { + correctReqWidthOfChild (child, requisition, allowDecreaseWidth); + correctReqHeightOfChild (child, requisition, splitHeightFun, + allowDecreaseHeight); + bool changed = correctReqAspectRatio (pass, child, requisition, + allowDecreaseWidth, allowDecreaseHeight, splitHeightFun); + + /* No need to repeat more passes */ + if (!changed) + break; + } } else { DBG_OBJ_MSG ("resize", 1, "delegated to (effective) container"); DBG_OBJ_MSG_START (); @@ -1876,10 +1921,101 @@ void Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition, requisition->descent); } -void Widget::setReqWidth (Requisition *requisition, int width) -{ - /* Naive implementation */ - requisition->width = width; +/** + * Correct a child requisition aspect ratio if needed. + * + * After the parent widget corrects the requisition provided by the \param + * child, the preferred aspect ratio may no longer be the current ratio. This + * method tries to adjust the size of the requisition so the aspect ratio is the + * preferred aspect ratio of the child. + * + * It implements three passes: increase (0), decrease (1) or keep (2). In the + * increase pass, the size is increased to fill the aspect ratio. If after + * correcting the size, it is still not the preferred aspect ratio (maybe it + * breaks some other constraint), reducing the size will be attempted. If at the + * end, reducing the size doesn't fix the preferred aspect ratio, the size is + * kept as it is. + * + * It can be called from the parent or the child itself, as it doesn't read any + * information from the current widget. + * + * \return true if the requisition has been modified, false otherwise. + */ +bool Widget::correctReqAspectRatio (int pass, Widget *child, Requisition *requisition, + bool allowDecreaseWidth, bool allowDecreaseHeight, + void (*splitHeightFun) (int, int*, int*)) +{ + /* Only correct the requisition if both dimensions are set, otherwise is left + * to the child how to proceed. */ + int wReq = requisition->width; + int hReq = requisition->ascent + requisition->descent; + + /* Prevent division by 0 */ + bool sizeSet = wReq > 0 && hReq > 0; + + float ratio = child->ratio; + bool changed = false; + + DEBUG_MSG(1, "Widget::correctReqAspectRatio() -- wReq=%d, hReq=%d, pass=%d\n", + wReq, hReq, pass); + DEBUG_MSG(1, "child=%s, preferred ratio=%f\n", child->getClassName(), ratio); + + if (pass != PASS_KEEP && ratio > 0.0 && sizeSet) { + /* TODO: Ensure we are dealing with the content box rather than the + * margin box (as in the CSS box model). */ + + /* Compute the current ratio from the content box. */ + float curRatio = (float) wReq / (float) hReq; + DEBUG_MSG(1, "curRatio=%f, preferred ratio=%f\n", curRatio, ratio); + + if (curRatio < ratio) { + /* W is too small compared to H. Try to increase W or decrease H. */ + if (pass == PASS_INCREASE) { + /* Increase w */ + int w = (float) hReq * ratio; + requisition->width = w; + changed = true; + DEBUG_MSG(1, "increase w: %d -> %d\n", wReq, w); + } else if (pass == PASS_DECREASE) { + /* Decrease h */ + if (allowDecreaseHeight) { + /* FIXME: This may lose cases where allowDecreaseHeight is false, and + * the requisition has increased the height first, but we could still + * reduce the corrected hight above the original height, without + * making the requisition height smaller. */ + int h = (float) wReq / ratio; + splitHeightFun (h, &requisition->ascent, &requisition->descent); + changed = true; + DEBUG_MSG(1, "decrease h: %d -> %d\n", hReq, h); + } + } + } else if (curRatio > ratio) { + /* W is too big compared to H. Try to decrease W or increase H. */ + if (pass == PASS_INCREASE) { + /* Increase h */ + int h = (float) wReq / ratio; + splitHeightFun (h, &requisition->ascent, &requisition->descent); + changed = true; + DEBUG_MSG(1, "increase h: %d -> %d\n", hReq, h); + } else if (pass == PASS_DECREASE) { + /* Decrease w */ + if (allowDecreaseWidth) { + /* FIXME: This may lose cases where allowDecreaseWidth is false, and + * the requisition has increased the width first, but we could still + * reduce the corrected width above the original width, without + * making the requisition width smaller. */ + int w = (float) hReq * ratio; + requisition->width = w; + changed = true; + DEBUG_MSG(1, "decrease w: %d -> %d\n", wReq, w); + } + } + } else { + /* Current ratio is the preferred one. */ + } + } + + return changed; } /** Correct a child requisition to fit the parent. @@ -1901,14 +2037,8 @@ void Widget::correctReqWidthOfChild (Widget *child, Requisition *requisition, if (!allowDecreaseWidth && limitMinWidth < requisition->width) limitMinWidth = requisition->width; - int width = requisition->width; child->calcFinalWidth (child->getStyle(), -1, this, limitMinWidth, true, - &width); - - - /* Ask the child widget to adjust its own requisition in case it has to - * modify the height too (like images to try to preserve the aspect ratio) */ - child->setReqWidth(requisition, width); + &requisition->width); DBG_OBJ_LEAVE_VAL ("%d * (%d + %d)", requisition->width, requisition->ascent, requisition->descent); diff --git a/dw/widget.hh b/dw/widget.hh index a7daa91d..8631b9db 100644 --- a/dw/widget.hh +++ b/dw/widget.hh @@ -207,6 +207,12 @@ protected: inline int getContentWidth() { return allocation.width - boxDiffWidth (); } inline int getContentHeight() { return getHeight () - boxDiffHeight (); } + /** + * Preferred aspect ratio of the widget. Set to 0 when there is none. It is + * computed as width / height. + */ + float ratio; + Layout *layout; /** @@ -351,18 +357,24 @@ protected: virtual int getAvailWidthOfChild (Widget *child, bool forceValue); virtual int getAvailHeightOfChild (Widget *child, bool forceValue); - virtual void setReqWidth (Requisition *requisition, int width); + virtual void correctRequisitionOfChild (Widget *child, Requisition *requisition, void (*splitHeightFun) (int, int*, int*), bool allowDecreaseWidth, bool allowDecreaseHeight); + void correctRequisitionViewport (Requisition *requisition, + void (*splitHeightFun) (int, int*, int*), + bool allowDecreaseWidth, bool allowDecreaseHeight); void correctReqWidthOfChild (Widget *child, Requisition *requisition, bool allowDecreaseWidth); void correctReqHeightOfChild (Widget *child, Requisition *requisition, void (*splitHeightFun) (int, int*, int*), bool allowDecreaseHeight); + bool correctReqAspectRatio (int pass, Widget *child, Requisition *requisition, + bool allowDecreaseWidth, bool allowDecreaseHeight, + void (*splitHeightFun) (int, int*, int*)); virtual void correctExtremesOfChild (Widget *child, Extremes *extremes, bool useAdjustmentWidth); |