From 03da5405bcde2a900f760c85dc417f735896b5e2 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Fri, 11 May 2007 18:14:47 +0000 Subject: [PATCH] The beginnings of an app for testing the layout constraints of individual widgets. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@21110 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/tests/kits/interface/layout/Jamfile | 9 +- .../interface/layout/WidgetLayoutTest.cpp | 773 ++++++++++++++++++ 2 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 src/tests/kits/interface/layout/WidgetLayoutTest.cpp diff --git a/src/tests/kits/interface/layout/Jamfile b/src/tests/kits/interface/layout/Jamfile index 0cef967d0c..53ed73605d 100644 --- a/src/tests/kits/interface/layout/Jamfile +++ b/src/tests/kits/interface/layout/Jamfile @@ -8,8 +8,15 @@ SimpleTest LayoutTest1 : be ; +SimpleTest WidgetLayoutTest : + WidgetLayoutTest.cpp + : + be +; + if $(TARGET_PLATFORM) = libbe_test { - HaikuInstall install-test-apps : $(HAIKU_APP_TEST_DIR) : LayoutTest1 + HaikuInstall install-test-apps : $(HAIKU_APP_TEST_DIR) + : LayoutTest1 WidgetLayoutTest : tests!apps ; } diff --git a/src/tests/kits/interface/layout/WidgetLayoutTest.cpp b/src/tests/kits/interface/layout/WidgetLayoutTest.cpp new file mode 100644 index 0000000000..1541282cf1 --- /dev/null +++ b/src/tests/kits/interface/layout/WidgetLayoutTest.cpp @@ -0,0 +1,773 @@ +/* + * Copyright 2007, Ingo Weinhold . + * All rights reserved. Distributed under the terms of the MIT License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +// internal messages +enum { + MSG_2D_SLIDER_VALUE_CHANGED = '2dsv', +}; + + +// missing operators in BPoint + +BPoint +operator-(const BPoint& p) +{ + return BPoint(-p.x, -p.y); +} + + +BPoint +operator+(const BPoint& p, const BSize& size) +{ + return BPoint(p.x + size.width, p.y + size.height); +} + + +// View +class View { +public: + View(BRect frame) + : fFrame(frame), + fContainer(NULL), + fParent(NULL), + fChildren(), + fViewColor(B_TRANSPARENT_32_BIT) + { + } + + virtual ~View() + { + // delete children + for (int32 i = CountChildren() - 1; i >= 0; i--) + delete RemoveChild(i); + } + + virtual void SetFrame(BRect frame) + { + if (frame != fFrame && frame.IsValid()) { + BRect oldFrame(frame); + Invalidate(); + fFrame = frame; + Invalidate(); + + FrameChanged(oldFrame, frame); + } + } + + BRect Frame() const + { + return fFrame; + } + + BRect Bounds() const + { + return BRect(fFrame).OffsetToCopy(B_ORIGIN); + } + + void SetLocation(BPoint location) + { + SetFrame(fFrame.OffsetToCopy(location)); + } + + BPoint Location() const + { + return fFrame.LeftTop(); + } + + void SetSize(BSize size) + { + BRect frame(fFrame); + frame.right = frame.left + size.width; + frame.bottom = frame.top + size.height; + SetFrame(frame); + } + + BSize Size() const + { + return BSize(Frame()); + } + + BPoint LocationInContainer() const + { + BPoint location = fFrame.LeftTop(); + return (fParent ? fParent->LocationInContainer() + location : location); + } + + BRect FrameInContainer() const + { + BRect frame(fFrame); + return frame.OffsetToCopy(LocationInContainer()); + } + + BPoint ConvertFromContainer(BPoint point) const + { + return point - LocationInContainer(); + } + + BRect ConvertFromContainer(BRect rect) const + { + return rect.OffsetBySelf(-LocationInContainer()); + } + + BPoint ConvertToContainer(BPoint point) const + { + return point + LocationInContainer(); + } + + BRect ConvertToContainer(BRect rect) const + { + return rect.OffsetBySelf(LocationInContainer()); + } + + View* Parent() const + { + return fParent; + } + + BView* Container() const + { + return fContainer; + } + + bool AddChild(View* child) + { + if (!child) + return false; + + if (child->Parent() || child->Container()) { + fprintf(stderr, "View::AddChild(): view %p already has a parent " + "or is the container view\n", child); + return false; + } + + if (!fChildren.AddItem(child)) + return false; + + child->_AddedToParent(this); + + child->Invalidate(); + + return true; + } + + bool RemoveChild(View* child) + { + if (!child) + return false; + + return RemoveChild(IndexOfChild(child)); + } + + View* RemoveChild(int32 index) + { + if (index < 0 || index >= fChildren.CountItems()) + return NULL; + + View* child = ChildAt(index); + child->Invalidate(); + child->_RemovingFromParent(); + fChildren.RemoveItem(index); + + return child; + } + + int32 CountChildren() const + { + return fChildren.CountItems(); + } + + View* ChildAt(int32 index) const + { + return (View*)fChildren.ItemAt(index); + } + + View* ChildAt(BPoint point) const + { + for (int32 i = 0; View* child = ChildAt(i); i++) { + if (child->Frame().Contains(point)) + return child; + } + + return NULL; + } + + View* AncestorAt(BPoint point, BPoint* localPoint = NULL) const + { + if (!Bounds().Contains(point)) + return NULL; + + View* view = const_cast(this); + + // Iterate deeper down the hierarchy, until we reach a view that + // doesn't have a child at the location. + while (true) { + View* child = view->ChildAt(point); + if (!child) { + if (localPoint) + *localPoint = point; + return view; + } + + view = child; + point -= view->Frame().LeftTop(); + } + } + + int32 IndexOfChild(View* child) const + { + return (child ? fChildren.IndexOf(child) : -1); + } + + void Invalidate(BRect rect) + { + if (fContainer) { + rect = rect & Bounds(); + fContainer->Invalidate(rect.OffsetByCopy(LocationInContainer())); + } + } + + void Invalidate() + { + Invalidate(Bounds()); + } + + void SetViewColor(rgb_color color) + { + fViewColor = color; + } + + virtual void Draw(BView* container, BRect updateRect) + { + } + + virtual void MouseDown(BPoint where, uint32 buttons, int32 modifiers) + { + } + + virtual void MouseUp(BPoint where, uint32 buttons, int32 modifiers) + { + } + + virtual void MouseMoved(BPoint where, uint32 buttons, int32 modifiers) + { + } + + virtual void AddedToContainer() + { + } + + virtual void RemovingFromContainer() + { + } + + virtual void FrameChanged(BRect oldFrame, BRect newFrame) + { + } + +protected: + virtual void _AddedToParent(View* parent) + { + fParent = parent; + + if (parent->Container()) { + Invalidate(); + _AddedToContainer(parent->Container()); + } + } + + virtual void _RemovingFromParent() + { + if (fContainer) + _RemovingFromContainer(); + + fParent = NULL; + } + + virtual void _AddedToContainer(BView* container) + { + fContainer = container; + + AddedToContainer(); + + for (int32 i = 0; View* child = ChildAt(i); i++) + child->_AddedToContainer(fContainer); + } + + virtual void _RemovingFromContainer() + { + for (int32 i = 0; View* child = ChildAt(i); i++) + child->_RemovingFromContainer(); + + RemovingFromContainer(); + + fContainer = NULL; + } + + void _Draw(BView* container, BRect updateRect) + { +//printf("%p->View::_Draw(): ", this); +//updateRect.PrintToStream(); + + // compute the clipping region + BRegion region(Bounds()); + for (int32 i = 0; View* child = ChildAt(i); i++) + region.Exclude(child->Frame()); + + if (region.Frame().IsValid()) { + // set the clipping region + container->ConstrainClippingRegion(®ion); + + // draw the background, if it isn't transparent + if (fViewColor.alpha != 0) { + container->SetLowColor(fViewColor); + container->FillRect(updateRect, B_SOLID_LOW); + } + + // draw this view + Draw(container, updateRect); + + // revert the clipping region + region.Set(Bounds()); + container->ConstrainClippingRegion(®ion); + } + + // draw the children + if (CountChildren() > 0) { + container->PushState(); + + for (int32 i = 0; View* child = ChildAt(i); i++) { + BRect childFrame = child->Frame(); + BRect childUpdateRect = updateRect & childFrame; + if (childUpdateRect.IsValid()) { + // set origin + childUpdateRect.OffsetBy(-childFrame.LeftTop()); + container->SetOrigin(childFrame.LeftTop()); + + // draw + child->_Draw(container, childUpdateRect); + } + } + + container->PopState(); + } + } + +private: + BRect fFrame; + BView* fContainer; + View* fParent; + BList fChildren; + rgb_color fViewColor; +}; + + +// ViewContainer +class ViewContainer : public BView, public View { +public: + ViewContainer(BRect frame) + : BView(frame, "view container", B_FOLLOW_NONE, B_WILL_DRAW), + View(frame.OffsetToCopy(B_ORIGIN)), + fMouseFocus(NULL) + { + BView::SetViewColor(B_TRANSPARENT_32_BIT); + _AddedToContainer(this); + } + + virtual void Draw(BRect updateRect) + { + View::_Draw(this, updateRect); + } + + virtual void MouseDown(BPoint where) + { +//printf("ViewContainer::MouseDown((%f, %f))\n", where.x, where.y); + // get mouse buttons and modifiers + uint32 buttons; + int32 modifiers; + _GetButtonsAndModifiers(buttons, modifiers); + + // get mouse focus + if (!fMouseFocus && (buttons & B_PRIMARY_MOUSE_BUTTON)) + fMouseFocus = AncestorAt(where); +//printf(" mouse focus: %p (this: %p)\n", fMouseFocus, this); + + // call hook + if (fMouseFocus) { + fMouseFocus->MouseDown(fMouseFocus->ConvertFromContainer(where), + buttons, modifiers); + } + } + + virtual void MouseUp(BPoint where) + { +//printf("ViewContainer::MouseUp((%f, %f))\n", where.x, where.y); + if (!fMouseFocus) + return; + + // get mouse buttons and modifiers + uint32 buttons; + int32 modifiers; + _GetButtonsAndModifiers(buttons, modifiers); + + // call hook + if (fMouseFocus) { + fMouseFocus->MouseUp(fMouseFocus->ConvertFromContainer(where), + buttons, modifiers); + } + + // unset the mouse focus when the primary button has been released + if (!(buttons & B_PRIMARY_MOUSE_BUTTON)) + fMouseFocus = NULL; + } + + virtual void MouseMoved(BPoint where, uint32 code, const BMessage* message) + { +//printf("ViewContainer::MouseMoved((%f, %f))\n", where.x, where.y); + if (!fMouseFocus) + return; + + // get mouse buttons and modifiers + uint32 buttons; + int32 modifiers; + _GetButtonsAndModifiers(buttons, modifiers); + + // call hook + if (fMouseFocus) { + fMouseFocus->MouseMoved(fMouseFocus->ConvertFromContainer(where), + buttons, modifiers); + } + } + + virtual void Draw(BView* container, BRect updateRect) + { + } + + virtual void MouseDown(BPoint where, uint32 buttons, int32 modifiers) + { + } + + virtual void MouseUp(BPoint where, uint32 buttons, int32 modifiers) + { + } + + virtual void MouseMoved(BPoint where, uint32 buttons, int32 modifiers) + { + } + +private: + void _GetButtonsAndModifiers(uint32& buttons, int32& modifiers) + { + buttons = 0; + modifiers = 0; + + if (BMessage* message = Window()->CurrentMessage()) { + if (message->FindInt32("buttons", (int32*)&buttons) != B_OK) + buttons = 0; + if (message->FindInt32("modifiers", modifiers) != B_OK) + modifiers = 0; + } + } + +private: + View* fMouseFocus; +}; + + +// TwoDimensionalSliderView +class TwoDimensionalSliderView : public View, public BInvoker { +public: + TwoDimensionalSliderView(BMessage* message = NULL, + BMessenger target = BMessenger()) + : View(BRect(0, 0, 4, 4)), + BInvoker(message, target), + fMinLocation(0, 0), + fMaxLocation(0, 0), + fDragging(false) + { + SetViewColor((rgb_color){0, 120, 0, 255}); + } + + void SetLocationRange(BPoint minLocation, BPoint maxLocation) + { + if (maxLocation.x < minLocation.x) + maxLocation.x = minLocation.x; + if (maxLocation.y < minLocation.y) + maxLocation.y = minLocation.y; + + fMinLocation = minLocation; + fMaxLocation = maxLocation; + + // force valid value + SetValue(Value()); + } + + BPoint MinLocation() const + { + return fMinLocation; + } + + BPoint MaxLocation() const + { + return fMaxLocation; + } + + BPoint Value() const + { + return Location() - fMinLocation; + } + + void SetValue(BPoint value) + { + BPoint location = fMinLocation + value; + if (location.x < fMinLocation.x) + location.x = fMinLocation.x; + if (location.y < fMinLocation.y) + location.y = fMinLocation.y; + if (location.x > fMaxLocation.x) + location.x = fMaxLocation.x; + if (location.y > fMaxLocation.y) + location.y = fMaxLocation.y; + + if (location != Location()) { + SetFrame(Frame().OffsetToCopy(location)); + + // send the message + if (Message()) { + BMessage message(*Message()); + message.AddPoint("value", Value()); + InvokeNotify(&message); + } + } + } + + virtual void MouseDown(BPoint where, uint32 buttons, int32 modifiers) + { + if (fDragging) + return; + + fOriginalLocation = Frame().LeftTop(); + fOriginalPoint = ConvertToContainer(where); + fDragging = true; + } + + virtual void MouseUp(BPoint where, uint32 buttons, int32 modifiers) + { + if (!fDragging || (buttons & B_PRIMARY_MOUSE_BUTTON)) + return; + + fDragging = false; + } + + virtual void MouseMoved(BPoint where, uint32 buttons, int32 modifiers) + { + if (!fDragging) + return; + + BPoint moved = ConvertToContainer(where) - fOriginalPoint; + SetValue(fOriginalLocation - fMinLocation + moved); + } + +private: + BPoint fMinLocation; + BPoint fMaxLocation; + bool fDragging; + BPoint fOriginalPoint; + BPoint fOriginalLocation; +}; + + +// WrapperView +class WrapperView : public View { +public: + WrapperView(BRect frame, BView* view) + : View(frame), + fView(view), + fInsets(1, 1, 1, 1) + { + SetViewColor((rgb_color){255, 0, 0, 255}); + } + + BView* GetView() const + { + return fView; + } + + BSize MinSize() const + { + return _FromViewSize(fView->MinSize()); + } + + BSize MaxSize() const + { + return _FromViewSize(fView->MaxSize()); + } + + BSize PreferredSize() const + { + return _FromViewSize(fView->PreferredSize()); + } + + virtual void AddedToContainer() + { + _UpdateViewFrame(); + + Container()->AddChild(fView); + } + + virtual void RemovingFromContainer() + { + Container()->RemoveChild(fView); + } + + virtual void FrameChanged(BRect oldFrame, BRect newFrame) + { + _UpdateViewFrame(); + } + +private: + void _UpdateViewFrame() + { + BRect frame(_ViewFrameInContainer()); + fView->MoveTo(frame.LeftTop()); + fView->ResizeTo(frame.Width(), frame.Height()); + } + + BRect _ViewFrame() const + { + BRect viewFrame(Bounds()); + viewFrame.left += fInsets.left; + viewFrame.top += fInsets.top; + viewFrame.right -= fInsets.right; + viewFrame.bottom -= fInsets.bottom; + + return viewFrame; + } + + BRect _ViewFrameInContainer() const + { + return ConvertToContainer(_ViewFrame()); + } + + BSize _FromViewSize(BSize size) const + { + float horizontalInsets = fInsets.left + fInsets.right - 1; + float verticalInsets = fInsets.top + fInsets.bottom - 1; + return BSize(BLayoutUtils::AddDistances(size.width, horizontalInsets), + BLayoutUtils::AddDistances(size.height, verticalInsets)); + } + +private: + BView* fView; + BRect fInsets; +}; + + +// TestWindow +class TestWindow : public BWindow { +public: + TestWindow() + : BWindow(BRect(100, 100, 700, 500), "Widget Layout", + B_TITLED_WINDOW, + B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE | B_NOT_RESIZABLE + | B_NOT_ZOOMABLE) + { + ViewContainer* viewContainer = new ViewContainer(Bounds()); + viewContainer->View::SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(viewContainer); + + View* view = new View(BRect(10, 10, 400, 300)); + viewContainer->View::AddChild(view); + view->SetViewColor((rgb_color){200, 200, 240, 255}); + + // wrapper view + fWrapperView = new WrapperView(BRect(10, 10, 100, 100), + new BButton(BRect(0, 0, 9, 9), "test button", "Ooh, press me!", + (BMessage*)NULL, B_FOLLOW_NONE, + B_WILL_DRAW | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE)); +fWrapperView->GetView()->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); + view->AddChild(fWrapperView); + fWrapperView->SetSize(fWrapperView->PreferredSize()); + + // slider view + fSliderView = new TwoDimensionalSliderView( + new BMessage(MSG_2D_SLIDER_VALUE_CHANGED), this); + fSliderView->SetLocationRange(BPoint(30, 40), BPoint(150, 130)); + view->AddChild(fSliderView); + + _UpdateSliderConstraints(); + } + + virtual void MessageReceived(BMessage* message) + { + switch (message->what) { + case MSG_2D_SLIDER_VALUE_CHANGED: + { +// message->PrintToStream(); + BPoint sliderLocation(fSliderView->Location()); + BPoint wrapperLocation(fWrapperView->Location()); + BSize size(sliderLocation.x - wrapperLocation.x - 1, + sliderLocation.y - wrapperLocation.y - 1); + fWrapperView->SetSize(size); + break; + } + + default: + BWindow::MessageReceived(message); + break; + } + } + +private: + void _UpdateSliderConstraints() + { + BPoint wrapperLocation(fWrapperView->Location()); + BSize minWrapperSize(fWrapperView->MinSize()); + BSize maxWrapperSize(fWrapperView->MaxSize()); + + BPoint minSliderLocation = wrapperLocation + minWrapperSize + + BPoint(1, 1); + BPoint maxSliderLocation = wrapperLocation + maxWrapperSize + + BPoint(1, 1); + + BSize sliderSize(fSliderView->Size()); + BSize parentSize(fSliderView->Parent()->Size()); + if (maxSliderLocation.x + sliderSize.width > parentSize.width) + maxSliderLocation.x = parentSize.width - sliderSize.width; + if (maxSliderLocation.y + sliderSize.height > parentSize.height) + maxSliderLocation.y = parentSize.height - sliderSize.height; + + fSliderView->SetLocationRange(minSliderLocation, maxSliderLocation); + } + +private: + WrapperView* fWrapperView; + TwoDimensionalSliderView* fSliderView; +}; + + +int +main() +{ + BApplication app("application/x-vnd.haiku.widget-layout-test"); + + BWindow* window = new TestWindow; + window->Show(); + + app.Run(); + + return 0; +}