View: provide the transform between different coordinate spaces

There's currently no way for an application to convert between view and
drawing coordinates with a drawing states stack without keeping track of
all the transformations itself, which is not very convenient for helper
or library functions.

Handle other spaces too, for good measure.

Change-Id: Ic8404a1c111e273fff1eebf2f9f59f58246b796c
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5775
Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
Reviewed-by: waddlesplash <waddlesplash@gmail.com>
(cherry picked from commit 241f109ccb)
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5787
This commit is contained in:
Máximo Castañeda 2022-11-01 21:02:18 +01:00 committed by waddlesplash
parent ed1a2247d7
commit f7a4f667a4
7 changed files with 199 additions and 4 deletions

View File

@ -272,6 +272,74 @@
*/
// coordinate spaces
/*!
\typedef coordinate_space
\brief A coordinate or drawing space.
\see \ref coordinatespaces
\since Haiku R1
*/
/*!
\var B_CURRENT_STATE_COORDINATES
\brief The current drawing space.
\since Haiku R1
*/
/*!
\var B_PREVIOUS_STATE_COORDINATES
\brief The drawing space of the parent state in the stack.
\since Haiku R1
*/
/*!
\var B_VIEW_COORDINATES
\brief The base coordinate space of the view.
\since Haiku R1
*/
/*!
\var B_PARENT_VIEW_DRAW_COORDINATES
\brief The current drawing space of the parent view.
\since Haiku R1
*/
/*!
\var B_PARENT_VIEW_COORDINATES
\brief The base coordinate space of the parent view.
\since Haiku R1
*/
/*!
\var B_WINDOW_COORDINATES
\brief The coordinate space of the owning window.
\since Haiku R1
*/
/*!
\var B_SCREEN_COORDINATES
\brief The coordinate space of the screen.
\since Haiku R1
*/
// view flags
@ -1903,6 +1971,27 @@ SetViewColor(Parent()->ViewColor());
*/
/*!
\fn BAffineTransform BView::TransformTo(coordinate_space basis) const
\brief Return the BAffineTransform to convert from the current drawing
space to \a basis.
\c B_PREVIOUS_STATE_COORDINATES is equivalent to
\c B_CURRENT_STATE_COORDINATES when there is no parent state.
\c B_PARENT_VIEW_DRAW_COORDINATES and \c B_PARENT_VIEW_COORDINATES are
equivalent to \c B_WINDOW_COORDINATES when used on a root view.
\c B_WINDOW_COORDINATES works as \c B_SCREEN_COORDINATES for unattached
views.
\sa Transform()
\sa \ref coordinatespaces
\since Haiku R1
*/
/*!
\fn void BView::TranslateBy(double x, double y)
\brief Translate the current view by coordinates.

View File

@ -66,7 +66,5 @@
All drawing operations in a BView use coordinates specified in the drawing space.
However, the update rect passed to the Draw method (or passed to the Invalidate
method) is in the BView base coordinate space. Conversion between the two can
be done using the affine transform returned by BView::Transform, or in case the
legacy transformation functions are used, by applying the scale and origin returned
by the Scale() and Origin() functions.
be done using the affine transform returned by BView::TransformTo.
*/

View File

@ -70,6 +70,16 @@ enum {
B_FONT_ALL = 0x000001FF
};
typedef enum {
B_CURRENT_STATE_COORDINATES,
B_PREVIOUS_STATE_COORDINATES,
B_VIEW_COORDINATES,
B_PARENT_VIEW_DRAW_COORDINATES,
B_PARENT_VIEW_COORDINATES,
B_WINDOW_COORDINATES,
B_SCREEN_COORDINATES
} coordinate_space;
// view flags
const uint32 B_FULL_UPDATE_ON_RESIZE = 0x80000000UL; /* 31 */
const uint32 _B_RESERVED1_ = 0x40000000UL; /* 30 */
@ -324,6 +334,8 @@ public:
void ScaleBy(double x, double y);
void RotateBy(double angleRadians);
BAffineTransform TransformTo(coordinate_space basis) const;
void PushState();
void PopState();

View File

@ -358,6 +358,7 @@ enum {
// transformation in addition to origin/scale
AS_VIEW_SET_TRANSFORM,
AS_VIEW_GET_TRANSFORM,
AS_VIEW_GET_PARENT_COMPOSITE,
AS_VIEW_AFFINE_TRANSLATE,
AS_VIEW_AFFINE_SCALE,

View File

@ -45,6 +45,7 @@ enum {
B_VIEW_WHICH_VIEW_COLOR_BIT = 0x00100000,
B_VIEW_WHICH_LOW_COLOR_BIT = 0x00200000,
B_VIEW_WHICH_HIGH_COLOR_BIT = 0x00400000,
B_VIEW_PARENT_COMPOSITE_BIT = 0x00800000,
B_VIEW_ALL_BITS = 0x00ffffff,
@ -131,6 +132,11 @@ class ViewState {
float scale;
BAffineTransform transform;
// composite transformation stack
BPoint parent_composite_origin;
float parent_composite_scale;
BAffineTransform parent_composite_transform;
// line modes
join_mode line_join;
cap_mode line_cap;

View File

@ -170,6 +170,10 @@ ViewState::ViewState()
font_flags = font.Flags();
font_aliasing = false;
parent_composite_transform.Reset();
parent_composite_scale = 1.0f;
parent_composite_origin.Set(0, 0);
// We only keep the B_VIEW_CLIP_REGION_BIT flag invalidated,
// because we should get the clipping region from app_server.
// The other flags do not need to be included because the data they
@ -340,7 +344,8 @@ ViewState::UpdateFrom(BPrivate::PortLink &link)
clipping_region_used = false;
}
valid_flags = ~B_VIEW_CLIP_REGION_BIT;
valid_flags = ~(B_VIEW_CLIP_REGION_BIT | B_VIEW_PARENT_COMPOSITE_BIT)
| (valid_flags & B_VIEW_PARENT_COMPOSITE_BIT);
}
} // namespace BPrivate
@ -1837,6 +1842,8 @@ BView::PushState()
fOwner->fLink->StartMessage(AS_VIEW_PUSH_STATE);
fState->valid_flags &= ~B_VIEW_PARENT_COMPOSITE_BIT;
// initialize origin, scale and transform, new states start "clean".
fState->valid_flags |= B_VIEW_SCALE_BIT | B_VIEW_ORIGIN_BIT
| B_VIEW_TRANSFORM_BIT;
@ -1989,6 +1996,71 @@ BView::Transform() const
}
BAffineTransform
BView::TransformTo(coordinate_space basis) const
{
if (basis == B_CURRENT_STATE_COORDINATES)
return B_AFFINE_IDENTITY_TRANSFORM;
if (!fState->IsValid(B_VIEW_PARENT_COMPOSITE_BIT) && fOwner != NULL) {
_CheckLockAndSwitchCurrent();
fOwner->fLink->StartMessage(AS_VIEW_GET_PARENT_COMPOSITE);
int32 code;
if (fOwner->fLink->FlushWithReply(code) == B_OK && code == B_OK) {
fOwner->fLink->Read<BAffineTransform>(&fState->parent_composite_transform);
fOwner->fLink->Read<float>(&fState->parent_composite_scale);
fOwner->fLink->Read<BPoint>(&fState->parent_composite_origin);
}
fState->valid_flags |= B_VIEW_PARENT_COMPOSITE_BIT;
}
BAffineTransform transform = fState->parent_composite_transform * Transform();
float scale = fState->parent_composite_scale * Scale();
transform.PreScaleBy(scale, scale);
BPoint origin = Origin();
origin.x *= fState->parent_composite_scale;
origin.y *= fState->parent_composite_scale;
origin += fState->parent_composite_origin;
transform.TranslateBy(origin);
if (basis == B_PREVIOUS_STATE_COORDINATES) {
transform.TranslateBy(-fState->parent_composite_origin);
transform.PreMultiplyInverse(fState->parent_composite_transform);
transform.ScaleBy(1.0f / fState->parent_composite_scale);
return transform;
}
if (basis == B_VIEW_COORDINATES)
return transform;
origin = B_ORIGIN;
if (basis == B_PARENT_VIEW_COORDINATES || basis == B_PARENT_VIEW_DRAW_COORDINATES) {
BView* parent = Parent();
if (parent != NULL) {
ConvertToParent(&origin);
transform.TranslateBy(origin);
if (basis == B_PARENT_VIEW_DRAW_COORDINATES)
transform = transform.PreMultiplyInverse(parent->TransformTo(B_VIEW_COORDINATES));
return transform;
}
basis = B_WINDOW_COORDINATES;
}
ConvertToScreen(&origin);
if (basis == B_WINDOW_COORDINATES) {
BWindow* window = Window();
if (window != NULL)
origin -= window->Frame().LeftTop();
}
transform.TranslateBy(origin);
return transform;
}
void
BView::TranslateBy(double x, double y)
{

View File

@ -1640,6 +1640,23 @@ fDesktop->LockSingleWindow();
fLink.Flush();
break;
}
case AS_VIEW_GET_PARENT_COMPOSITE:
{
DrawState* state = fCurrentView->CurrentState()->PreviousState();
fLink.StartMessage(B_OK);
if (state != NULL) {
fLink.Attach<BAffineTransform>(state->CombinedTransform());
fLink.Attach<float>(state->CombinedScale());
fLink.Attach<BPoint>(state->CombinedOrigin());
} else {
fLink.Attach<BAffineTransform>(BAffineTransform());
fLink.Attach<float>(1.0f);
fLink.Attach<BPoint>(B_ORIGIN);
}
fLink.Flush();
break;
}
case AS_VIEW_AFFINE_TRANSLATE:
{
double x, y;