diff --git a/src/tests/servers/app/newerClipping/Desktop.cpp b/src/tests/servers/app/newerClipping/Desktop.cpp new file mode 100644 index 0000000000..a132faeb3d --- /dev/null +++ b/src/tests/servers/app/newerClipping/Desktop.cpp @@ -0,0 +1,573 @@ + +#include + +#include +#include +#include +#include +#include + +#include "DrawingEngine.h" +#include "WindowLayer.h" + +#include "Desktop.h" + +// constructor +Desktop::Desktop(DrawView* drawView) + : BLooper("desktop"), + fTracking(false), + fLastMousePos(-1.0, -1.0), + fClickedWindow(NULL), + fResizing(false), + fIs2ndButton(false), + + fClippingLock("clipping lock"), + fDirtyRegion(), + fBackgroundRegion(), + + fDrawView(drawView), + fDrawingEngine(fDrawView->GetDrawingEngine()), + + fWindows(64) +{ + fDrawView->SetDesktop(this); + + BRegion stillAvailableOnScreen; + _RebuildClippingForAllWindows(&stillAvailableOnScreen); + _SetBackground(&stillAvailableOnScreen); +} + +// destructor +Desktop::~Desktop() +{ + int32 count = CountWindows(); + for (int32 i = count - 1; i >= 0; i--) { + WindowLayer* window = WindowAtFast(i); + window->Lock(); + window->Quit(); + } +} + +// Draw +void +Desktop::Draw(BRect updateRect) +{ + // since parts of the view might have been exposed, + // we need a clipping rebuild + if (LockClipping()) { + BRegion background; + _RebuildClippingForAllWindows(&background); + _SetBackground(&background); + + UnlockClipping(); + } + if (fDrawingEngine->Lock()) { + fDrawingEngine->SetHighColor(51, 102, 152); + fDrawingEngine->FillRegion(&fBackgroundRegion); + fDrawingEngine->Unlock(); + } + + // trigger redrawing windows + BRegion update(updateRect); + update.Exclude(&fBackgroundRegion); + MarkDirty(&update); +} + +// MouseDown +void +Desktop::MouseDown(BPoint where, uint32 buttons) +{ + fLastMousePos = where; + fClickedWindow = WindowAt(where); + fClickTime = system_time(); + if (buttons == B_PRIMARY_MOUSE_BUTTON) { + fTracking = true; + if (fClickedWindow) { + BRect frame(fClickedWindow->Frame()); + BRect resizeRect(frame.right - 10, frame.bottom - 10, + frame.right + 4, frame.bottom + 4); + fResizing = resizeRect.Contains(where); + } + } else if (buttons == B_SECONDARY_MOUSE_BUTTON) { + if (fClickedWindow) + SendToBack(fClickedWindow); + + fIs2ndButton = true; + } else if (buttons == B_TERTIARY_MOUSE_BUTTON) { + if (modifiers() & B_SHIFT_KEY) { + // render global dirty region + if (fDrawingEngine->Lock()) { + fDrawingEngine->SetHighColor(255, 0, 0); + fDrawingEngine->FillRegion(&fDirtyRegion); + fDrawingEngine->MarkDirty(&fDirtyRegion); + fDrawingEngine->Unlock(); + } + } else { + // complete redraw +#if RUN_WITH_FRAME_BUFFER + // TODO: broken + BRegion region(Bounds()); + MarkDirty(®ion); + region = fBackgroundRegion; + fBackgroundRegion.MakeEmpty(); + _SetBackground(®ion); +#else + fDrawingEngine->MarkDirty(); +#endif + } + } +} + +// MouseUp +void +Desktop::MouseUp(BPoint where) +{ + if (!fIs2ndButton && system_time() - fClickTime < 250000L && fClickedWindow) { + BringToFront(fClickedWindow); + } + fTracking = false; + fIs2ndButton = false; + fClickedWindow = NULL; +} + +// MouseMoved +void +Desktop::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) +{ + if (fTracking) { + int32 dx = (int32)(where.x - fLastMousePos.x); + int32 dy = (int32)(where.y - fLastMousePos.y); + fLastMousePos = where; + + if (dx != 0 || dy != 0) { + if (fClickedWindow) { + if (fResizing) { + ResizeWindowBy(fClickedWindow, dx, dy); + } else { + MoveWindowBy(fClickedWindow, dx, dy); + } + } + } + } else if (fIs2ndButton) { + if (fDrawingEngine->Lock()) { + fDrawingEngine->SetHighColor(0, 0, 0); + fDrawingEngine->StrokeLine(fLastMousePos, where); + + BRect dirty(fLastMousePos, where); + if (dirty.left > dirty.right) { + dirty.left = where.x; + dirty.right = fLastMousePos.x; + } + if (dirty.top > dirty.bottom) { + dirty.top = where.y; + dirty.bottom = fLastMousePos.y; + } + fDrawingEngine->MarkDirty(dirty); + + fDrawingEngine->Unlock(); + } + fLastMousePos = where; + } +} + +// MessageReceived +void +Desktop::MessageReceived(BMessage* message) +{ + switch (message->what) { + case B_MOUSE_DOWN: { + BPoint where; + uint32 buttons; + if (message->FindPoint("where", &where) >= B_OK && + message->FindInt32("buttons", (int32*)&buttons) >= B_OK) { + + MouseDown(where, buttons); + } + break; + } + case B_MOUSE_UP: { + BPoint where; + if (message->FindPoint("where", &where) >= B_OK) { + + MouseUp(where); + } + break; + } + case B_MOUSE_MOVED: { + if (!MessageQueue()->FindMessage(B_MOUSE_MOVED, 0)) { + BPoint where; + uint32 transit; + if (message->FindPoint("where", &where) >= B_OK && + message->FindInt32("be:transit", (int32*)&transit) >= B_OK) { + + MouseMoved(where, transit, NULL); + } + } + break; + } + case MSG_DRAW: { + BRect area; + if (message->FindRect("area", &area) >= B_OK) + Draw(area); + } + case MSG_ADD_WINDOW: { + WindowLayer* window; + if (message->FindPointer("window", (void**)&window) >= B_OK) + AddWindow(window); + break; + } + default: + BLooper::MessageReceived(message); + } +} + +#pragma mark - + +// AddWindow +bool +Desktop::AddWindow(WindowLayer* window) +{ + bool success = false; + if (fWindows.AddItem((void*)window)) { + // rebuild the entire screen clipping and draw the new window + if (LockClipping()) { + BRegion background; + _RebuildClippingForAllWindows(&background); + fBackgroundRegion.Exclude(&window->VisibleRegion()); + MarkDirty(&window->VisibleRegion()); + _SetBackground(&background); + + UnlockClipping(); + } + success = true; + } + return success; +} + +// RemoveWindow +bool +Desktop::RemoveWindow(WindowLayer* window) +{ + bool success = false; + if (fWindows.RemoveItem((void*)window)) { + // rebuild the entire screen clipping and redraw the exposed windows + if (LockClipping()) { + BRegion dirty = window->VisibleRegion(); + BRegion background; + _RebuildClippingForAllWindows(&background); + MarkDirty(&dirty); + _SetBackground(&background); + + UnlockClipping(); + } + success = true; + } + return success; +} + +// IndexOf +int32 +Desktop::IndexOf(WindowLayer* window) const +{ + return fWindows.IndexOf((void*)window); +} + +// CountWindows +int32 +Desktop::CountWindows() const +{ + return fWindows.CountItems(); +} + +// HasWindow +bool +Desktop::HasWindow(WindowLayer* window) const +{ + return fWindows.HasItem((void*)window); +} + +// WindowAt +WindowLayer* +Desktop::WindowAt(int32 index) const +{ + return (WindowLayer*)fWindows.ItemAt(index); +} + +// WindowAtFast +WindowLayer* +Desktop::WindowAtFast(int32 index) const +{ + return (WindowLayer*)fWindows.ItemAtFast(index); +} + +// WindowAt +WindowLayer* +Desktop::WindowAt(const BPoint& where) const +{ + // NOTE, since the clipping is only changed from this thread, + // it is save to use it without locking + int32 count = CountWindows(); + for (int32 i = count - 1; i >= 0; i--) { + WindowLayer* window = WindowAtFast(i); + if (window->VisibleRegion().Contains(where)) + return window; + } + return NULL; +} + +// TopWindow +WindowLayer* +Desktop::TopWindow() const +{ + return (WindowLayer*)fWindows.LastItem(); +} + +// BottomWindow +WindowLayer* +Desktop::BottomWindow() const +{ + return (WindowLayer*)fWindows.FirstItem(); +} + +#pragma mark - + +// MoveWindowBy +void +Desktop::MoveWindowBy(WindowLayer* window, int32 x, int32 y) +{ + if (LockClipping()) { + // the dirty region starts with the visible area of the window being moved + BRegion newDirtyRegion(window->VisibleRegion()); + BRegion alreadyDirtyRegion(fDirtyRegion); + // we have to move along the part of the current dirty region + // that intersects with the window being moved + alreadyDirtyRegion.IntersectWith(&window->VisibleRegion()); + + window->MoveBy(x, y); + + BRegion background; + _RebuildClippingForAllWindows(&background); + + // construct the region that is possible to be blitted + // to move the contents of the window + BRegion copyRegion(window->VisibleRegion()); + copyRegion.OffsetBy(-x, -y); + copyRegion.IntersectWith(&newDirtyRegion); + + // include the the new visible region of the window being + // moved into the dirty region (for now) + newDirtyRegion.Include(&window->VisibleRegion()); + + if (fDrawingEngine->Lock()) { + fDrawingEngine->CopyRegion(©Region, x, y); + + // in the dirty region, exclude the parts that we + // could move by blitting + copyRegion.OffsetBy(x, y); + newDirtyRegion.Exclude(©Region); + fDrawingEngine->MarkDirty(©Region); + + fDrawingEngine->Unlock(); + } + // include the moved peviously dirty region +// TODO: redesign dirty regions to be located in +// each window -> less intersecting + alreadyDirtyRegion.OffsetBy(x, y); + newDirtyRegion.Include(&alreadyDirtyRegion); + + MarkDirty(&newDirtyRegion); + _SetBackground(&background); + + UnlockClipping(); + } +} + +// ResizeWindowBy +void +Desktop::ResizeWindowBy(WindowLayer* window, int32 x, int32 y) +{ + if (LockClipping()) { + BRegion newDirtyRegion; + BRegion previouslyOccupiedRegion(window->VisibleRegion()); + + window->ResizeBy(x, y, &newDirtyRegion); + + BRegion background; + _RebuildClippingForAllWindows(&background); + + previouslyOccupiedRegion.Exclude(&window->VisibleRegion()); + + newDirtyRegion.IntersectWith(&window->VisibleRegion()); + newDirtyRegion.Include(&previouslyOccupiedRegion); + + MarkDirty(&newDirtyRegion); + _SetBackground(&background); + + UnlockClipping(); + } +} + +// BringToFront +void +Desktop::BringToFront(WindowLayer* window) +{ + if (window == TopWindow()) + return; + + if (LockClipping()) { + + // we don't need to redraw what is currently + // visible of the window + BRegion clean(window->VisibleRegion()); + + // detach window and re-atach at last position + if (fWindows.RemoveItem((void*)window) && + fWindows.AddItem((void*)window)) { + + BRegion dummy; + _RebuildClippingForAllWindows(&dummy); + + // redraw what became visible of the window + BRegion dirty(window->VisibleRegion()); + dirty.Exclude(&clean); + + MarkDirty(&dirty); + } + + UnlockClipping(); + } +} + +// SendToBack +void +Desktop::SendToBack(WindowLayer* window) +{ + if (window == BottomWindow()) + return; + + if (LockClipping()) { + + // what is currently visible of the window + // might be dirty after the window is send to back + BRegion dirty(window->VisibleRegion()); + + // detach window and re-atach at last position + if (fWindows.RemoveItem((void*)window) && + fWindows.AddItem((void*)window, 0)) { + + BRegion dummy; + _RebuildClippingForAllWindows(&dummy); + + // redraw what was previously visible of the window + BRegion clean(window->VisibleRegion()); + dirty.Exclude(&clean); + + MarkDirty(&dirty); + } + + UnlockClipping(); + } +} + +#pragma mark - + +// MarkDirty +void +Desktop::MarkDirty(BRegion* region) +{ + if (region->CountRects() == 0) + return; + + // NOTE: the idea is that for all dirty areas ever included + // in the culmulative dirty region, redraw messages have been + // sent to the windows affected by just the newly included + // area. Therefor, _TriggerWindowRedrawing() is not called + // with "fDirtyRegion", but with just the new "region" instead. + // Whenever a window is actually carrying out a redraw request, + // it is expected to remove the redrawn area from the dirty region. + + if (LockClipping()) { + // add the new dirty region to the culmulative dirty region + fDirtyRegion.Include(region); + // send redraw messages to all windows intersecting the dirty region + _TriggerWindowRedrawing(region); + + UnlockClipping(); + } +} + +// MarkClean +void +Desktop::MarkClean(BRegion* region) +{ + if (LockClipping()) { + // remove the clean region from the culmulative dirty region + fDirtyRegion.Exclude(region); + + UnlockClipping(); + } +} + +#pragma mark - + +// _RebuildClippingForAllWindows +void +Desktop::_RebuildClippingForAllWindows(BRegion* stillAvailableOnScreen) +{ + // the available region on screen starts with the entire screen area + // each window on the screen will take a portion from that area + + // figure out what the entire screen area is + if (!fDrawView->Window()) + stillAvailableOnScreen->Set(fDrawView->Bounds()); + else { + if (fDrawView->Window()->Lock()) { + fDrawView->GetClippingRegion(stillAvailableOnScreen); + fDrawView->Window()->Unlock(); + } + } + + // set clipping of each window + int32 count = CountWindows(); + for (int32 i = count - 1; i >= 0; i--) { + WindowLayer* window = WindowAtFast(i); + window->SetClipping(stillAvailableOnScreen); + // that windows region is not available on screen anymore + stillAvailableOnScreen->Exclude(&window->VisibleRegion()); + } +} + +// _TriggerWindowRedrawing +void +Desktop::_TriggerWindowRedrawing(BRegion* newDirtyRegion) +{ + // send redraw messages to all windows intersecting the dirty region + int32 count = CountWindows(); + for (int32 i = count - 1; i >= 0; i--) { + WindowLayer* window = WindowAtFast(i); + if (newDirtyRegion->Intersects(window->VisibleRegion().Frame())) + window->PostMessage(MSG_REDRAW); + } +} + +// _SetBackground +void +Desktop::_SetBackground(BRegion* background) +{ + // remember the region not covered by any windows + // and redraw the dirty background + BRegion dirtyBackground(*background); + dirtyBackground.Exclude(&fBackgroundRegion); + dirtyBackground.IntersectWith(background); + fBackgroundRegion = *background; + if (dirtyBackground.Frame().IsValid()) { + if (fDrawingEngine->Lock()) { + fDrawingEngine->SetHighColor(51, 102, 152); + fDrawingEngine->FillRegion(&dirtyBackground); + fDrawingEngine->MarkDirty(&dirtyBackground); + + fDrawingEngine->Unlock(); + } + MarkClean(&dirtyBackground); + } +} diff --git a/src/tests/servers/app/newerClipping/Desktop.h b/src/tests/servers/app/newerClipping/Desktop.h new file mode 100644 index 0000000000..4a5f72f86f --- /dev/null +++ b/src/tests/servers/app/newerClipping/Desktop.h @@ -0,0 +1,111 @@ + +#ifndef DESKTOP_H +#define DESKTOP_H + +#include +#include +#include +#include +#include + +#include "DrawingEngine.h" +#include "MultiLocker.h" + +class WindowLayer; + +enum { + MSG_ADD_WINDOW = 'addw', + MSG_DRAW = 'draw', +}; + +#define MULTI_LOCKER 0 + +class Desktop : public BLooper { + public: + Desktop(DrawView* drawView); + virtual ~Desktop(); + + // functions for the DrawView + void Draw(BRect updateRect); + + void MouseDown(BPoint where, uint32 buttons); + void MouseUp(BPoint where); + void MouseMoved(BPoint where, uint32 code, + const BMessage* dragMessage); + + virtual void MessageReceived(BMessage* message); + + bool AddWindow(WindowLayer* window); + bool RemoveWindow(WindowLayer* window); + int32 IndexOf(WindowLayer* window) const; + int32 CountWindows() const; + bool HasWindow(WindowLayer* window) const; + + WindowLayer* WindowAt(int32 index) const; + WindowLayer* WindowAtFast(int32 index) const; + WindowLayer* WindowAt(const BPoint& where) const; + WindowLayer* TopWindow() const; + WindowLayer* BottomWindow() const; + + // doing something with the windows + void MoveWindowBy(WindowLayer* window, int32 x, int32 y); + void ResizeWindowBy(WindowLayer* window, int32 x, int32 y); + + void BringToFront(WindowLayer* window); + void SendToBack(WindowLayer* window); + +#if MULTI_LOCKER +# if 0 + bool ReadLockClipping() { return fClippingLock.ReadLock(); } + void ReadUnlockClipping() { fClippingLock.ReadUnlock(); } +# else + bool ReadLockClipping() { return fClippingLock.WriteLock(); } + void ReadUnlockClipping() { fClippingLock.WriteUnlock(); } +# endif +#else + bool ReadLockClipping() { return fClippingLock.LockWithTimeout(10000) >= B_OK; } + void ReadUnlockClipping() { fClippingLock.Unlock(); } +#endif + + bool LockClipping() { return fClippingLock.Lock(); } + void UnlockClipping() { fClippingLock.Unlock(); } + + void MarkDirty(BRegion* region); + void MarkClean(BRegion* region); + BRegion* DirtyRegion() + { return &fDirtyRegion; } + + DrawingEngine* GetDrawingEngine() const + { return fDrawingEngine; } + + BRegion& BackgroundRegion() + { return fBackgroundRegion; } + +private: + void _RebuildClippingForAllWindows(BRegion* stillAvailableOnScreen); + void _TriggerWindowRedrawing(BRegion* newDirtyRegion); + void _SetBackground(BRegion* background); + + bool fTracking; + BPoint fLastMousePos; + WindowLayer* fClickedWindow; + bool fResizing; + bigtime_t fClickTime; + bool fIs2ndButton; + +#if MULTI_LOCKER + MultiLocker fClippingLock; +#else + BLocker fClippingLock; +#endif + BRegion fDirtyRegion; + BRegion fBackgroundRegion; + + DrawView* fDrawView; + DrawingEngine* fDrawingEngine; + + BList fWindows; +}; + +#endif // DESKTOP_H + diff --git a/src/tests/servers/app/newerClipping/DrawingEngine.cpp b/src/tests/servers/app/newerClipping/DrawingEngine.cpp new file mode 100644 index 0000000000..f6659b5c10 --- /dev/null +++ b/src/tests/servers/app/newerClipping/DrawingEngine.cpp @@ -0,0 +1,311 @@ + +#include +#include + +#include +#include +#include + +#include "Desktop.h" + +#include "DrawingEngine.h" + +// constructor +DrawingEngine::DrawingEngine(BRect frame, DrawView* drawView) + : BView(frame, "drawing engine", B_FOLLOW_ALL, B_WILL_DRAW), + fDrawView(drawView) +{ +} + +// destructor +DrawingEngine::~DrawingEngine() +{ +} + +// Lock +bool +DrawingEngine::Lock() +{ + return Window()->Lock(); +} + +// Unlock +void +DrawingEngine::Unlock() +{ +#if RUN_WITH_FRAME_BUFFER + Sync(); +#else + Flush(); +#endif + Window()->Unlock(); +} + +// MarkDirty +void +DrawingEngine::MarkDirty(BRegion* region) +{ +#if RUN_WITH_FRAME_BUFFER + BRect frame = region->Frame(); + if (frame.IsValid()) { + BMessage message(MSG_INVALIDATE); + message.AddRect("area", frame); + fDrawView->Looper()->PostMessage(&message, fDrawView); + } +#endif +} + +// MarkDirty +void +DrawingEngine::MarkDirty(BRect rect) +{ +#if RUN_WITH_FRAME_BUFFER + if (rect.IsValid()) { + BMessage message(MSG_INVALIDATE); + message.AddRect("area", rect); + fDrawView->Looper()->PostMessage(&message, fDrawView); + } +#endif +} + +// MarkDirty +void +DrawingEngine::MarkDirty() +{ + if (Lock()) { + Invalidate(); + Unlock(); + } +} + +struct node { + node() + { + pointers = NULL; + } + node(const BRect& r, int32 maxPointers) + { + init(r, maxPointers); + } + ~node() + { + delete [] pointers; + } + + void init(const BRect& r, int32 maxPointers) + { + rect = r; + pointers = new node*[maxPointers]; + in_degree = 0; + next_pointer = 0; + } + + void push(node* node) + { + pointers[next_pointer] = node; + next_pointer++; + } + node* top() + { + return pointers[next_pointer]; + } + node* pop() + { + node* ret = top(); + next_pointer--; + return ret; + } + + BRect rect; + int32 in_degree; + node** pointers; + int32 next_pointer; +}; + +bool +is_left_of(const BRect& a, const BRect& b) +{ + return (a.right < b.left); +} +bool +is_above(const BRect& a, const BRect& b) +{ + return (a.bottom < b.top); +} + +void +DrawingEngine::CopyRegion(BRegion* region, int32 xOffset, int32 yOffset) +{ + int32 count = region->CountRects(); + + // TODO: make this step unnecessary + // (by using different stack impl inside node) + node nodes[count]; + for (int32 i= 0; i < count; i++) { + nodes[i].init(region->RectAt(i), count); + } + + for (int32 i = 0; i < count; i++) { + BRect a = region->RectAt(i); + for (int32 k = i + 1; k < count; k++) { + BRect b = region->RectAt(k); + int cmp = 0; + // compare horizontally + if (xOffset > 0) { + if (is_left_of(a, b)) { + cmp -= 1; + } else if (is_left_of(b, a)) { + cmp += 1; + } + } else if (xOffset < 0) { + if (is_left_of(a, b)) { + cmp += 1; + } else if (is_left_of(b, a)) { + cmp -= 1; + } + } + // compare vertically + if (yOffset > 0) { + if (is_above(a, b)) { + cmp -= 1; + } else if (is_above(b, a)) { + cmp += 1; + } + } else if (yOffset < 0) { + if (is_above(a, b)) { + cmp += 1; + } else if (is_above(b, a)) { + cmp -= 1; + } + } + // add appropriate node as successor + if (cmp > 0) { + nodes[i].push(&nodes[k]); + nodes[k].in_degree++; + } else if (cmp < 0) { + nodes[k].push(&nodes[i]); + nodes[i].in_degree++; + } + } + } + // put all nodes onto a stack that have an "indegree" count of zero + stack inDegreeZeroNodes; + for (int32 i = 0; i < count; i++) { + if (nodes[i].in_degree == 0) { + inDegreeZeroNodes.push(&nodes[i]); + } + } + // pop the rects from the stack, do the actual copy operation + // and decrease the "indegree" count of the other rects not + // currently on the stack and to which the current rect pointed + // to. If their "indegree" count reaches zero, put them onto the + // stack as well. + + while (!inDegreeZeroNodes.empty()) { + node* n = inDegreeZeroNodes.top(); + inDegreeZeroNodes.pop(); + + CopyBits(n->rect, BRect(n->rect).OffsetByCopy(xOffset, yOffset)); + + for (int32 k = 0; k < n->next_pointer; k++) { + n->pointers[k]->in_degree--; + if (n->pointers[k]->in_degree == 0) + inDegreeZeroNodes.push(n->pointers[k]); + } + } +} + +// constructor +DrawView::DrawView(BRect frame) +#if RUN_WITH_FRAME_BUFFER + : BView(frame, "desktop", B_FOLLOW_ALL, B_WILL_DRAW), +#else + : DrawingEngine(frame, NULL), +#endif + fDesktop(NULL), + +#if RUN_WITH_FRAME_BUFFER + fFrameBuffer(new BBitmap(Bounds(), B_RGB32, true)), + fDrawingEngine(new DrawingEngine(Bounds(), this)), +#else + fDrawingEngine(this) +#endif +{ + SetViewColor(B_TRANSPARENT_COLOR); +#if RUN_WITH_FRAME_BUFFER + if (fFrameBuffer->Lock()) { + fFrameBuffer->AddChild(fDrawingEngine); + fFrameBuffer->Unlock(); + } +#endif +} + +// destructor +DrawView::~DrawView() +{ +#if RUN_WITH_FRAME_BUFFER + delete fFrameBuffer; +#endif +} + +// MessageReceived +void +DrawView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case MSG_INVALIDATE: { + BRect area; + if (message->FindRect("area", &area) == B_OK) { + Invalidate(area); + } + break; + } + default: + BView::MessageReceived(message); + } +} + +// Draw +void +DrawView::Draw(BRect updateRect) +{ +#if RUN_WITH_FRAME_BUFFER + DrawBitmap(fFrameBuffer, updateRect, updateRect); +#else + BMessage message(MSG_DRAW); + message.AddRect("area", updateRect); + fDesktop->PostMessage(&message); +#endif +} + +// MouseDown +void +DrawView::MouseDown(BPoint where) +{ + SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); + + fDesktop->PostMessage(Window()->CurrentMessage()); +} + +// MouseUp +void +DrawView::MouseUp(BPoint where) +{ + fDesktop->PostMessage(Window()->CurrentMessage()); +} + +// MouseMoved +void +DrawView::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) +{ + fDesktop->PostMessage(Window()->CurrentMessage()); +} + +// SetDesktop +void +DrawView::SetDesktop(Desktop* desktop) +{ + fDesktop = desktop; +} + + diff --git a/src/tests/servers/app/newerClipping/DrawingEngine.h b/src/tests/servers/app/newerClipping/DrawingEngine.h new file mode 100644 index 0000000000..f8cbc3cd5c --- /dev/null +++ b/src/tests/servers/app/newerClipping/DrawingEngine.h @@ -0,0 +1,68 @@ + +#ifndef DRAWING_ENGINE_H +#define DRAWING_ENGINE_H + +#include + +class Desktop; +class DrawView; + +enum { + MSG_INVALIDATE = 'invl', +}; + +class DrawingEngine : public BView { + public: + DrawingEngine(BRect frame, DrawView* drawView); + virtual ~DrawingEngine(); + + bool Lock(); + void Unlock(); + + void CopyRegion(BRegion *region, int32 xOffset, int32 yOffset); + + void MarkDirty(BRegion* region); + void MarkDirty(BRect rect); + void MarkDirty(); + + private: + DrawView* fDrawView; +}; + +#define RUN_WITH_FRAME_BUFFER 0 + + +#if RUN_WITH_FRAME_BUFFER +class DrawView : public BView { +#else +class DrawView : public DrawingEngine { +#endif + + public: + DrawView(BRect frame); + virtual ~DrawView(); + + virtual void MessageReceived(BMessage* message); + + virtual void Draw(BRect updateRect); + + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 code, + const BMessage* dragMessage); + + void SetDesktop(Desktop* desktop); + DrawingEngine* GetDrawingEngine() const + { return fDrawingEngine; } + + private: + Desktop* fDesktop; + +#if RUN_WITH_FRAME_BUFFER + BBitmap* fFrameBuffer; +#endif + DrawingEngine* fDrawingEngine; +}; + +#endif // DRAWING_ENGINE_H + diff --git a/src/tests/servers/app/newerClipping/MultiLocker.cpp b/src/tests/servers/app/newerClipping/MultiLocker.cpp new file mode 100644 index 0000000000..3213bbf353 --- /dev/null +++ b/src/tests/servers/app/newerClipping/MultiLocker.cpp @@ -0,0 +1,464 @@ +/* MultiLocker.cpp */ +/* + Copyright 1999, Be Incorporated. All Rights Reserved. + This file may be used under the terms of the Be Sample Code License. +*/ + +#include "MultiLocker.h" + +#include +#include +#include + +//#define TIMING 1 +#define DEBUG 1 + + +MultiLocker::MultiLocker(const char* semaphoreBaseName) + : fInit(B_NO_INIT), + fReadCount(0), + fReadSem(-1), + fWriteCount(0), + fWriteSem(-1), + fLockCount(0), + fWriterLock(-1), + fWriterNest(0), + fWriterThread(-1), + fWriterStackBase(0), + fDebugArray(NULL), + fMaxThreads(0) +{ + //build the semaphores + if (semaphoreBaseName) { + char name[128]; + sprintf(name, "%s-%s", semaphoreBaseName, "ReadSem"); + fReadSem = create_sem(0, name); + sprintf(name, "%s-%s", semaphoreBaseName, "WriteSem"); + fWriteSem = create_sem(0, name); + sprintf(name, "%s-%s", semaphoreBaseName, "WriterLock"); + fWriterLock = create_sem(0, name); + } else { + fReadSem = create_sem(0, "MultiLocker_ReadSem"); + fWriteSem = create_sem(0, "MultiLocker_WriteSem"); + fWriterLock = create_sem(0, "MultiLocker_WriterLock"); + } + + if (fReadSem >= 0 && fWriteSem >=0 && fWriterLock >= 0) + fInit = B_OK; + + #if DEBUG + //we are in debug mode! + //create the reader tracking list + //the array needs to be large enough to hold all possible threads + system_info sys; + get_system_info(&sys); + fMaxThreads = sys.max_threads; + fDebugArray = (int32 *) malloc(fMaxThreads * sizeof(int32)); + for (int32 i = 0; i < fMaxThreads; i++) { + fDebugArray[i] = 0; + } + + #endif + #if TIMING + //initialize the counter variables + rl_count = ru_count = wl_count = wu_count = islock_count = 0; + rl_time = ru_time = wl_time = wu_time = islock_time = 0; + #if DEBUG + reg_count = unreg_count = 0; + reg_time = unreg_time = 0; + #endif + #endif +} + + +MultiLocker::~MultiLocker() +{ + //become the writer + if (!IsWriteLocked()) WriteLock(); + + //set locker to be uninitialized + fInit = B_NO_INIT; + + //delete the semaphores + delete_sem(fReadSem); + delete_sem(fWriteSem); + delete_sem(fWriterLock); + + #if DEBUG + //we are in debug mode! + //clear and delete the reader tracking list + free(fDebugArray); + #endif + #if TIMING + //let's produce some performance numbers + printf("MultiLocker Statistics:\n" + "Avg ReadLock: %lld\n" + "Avg ReadUnlock: %lld\n" + "Avg WriteLock: %lld\n" + "Avg WriteUnlock: %lld\n" + "Avg IsWriteLocked: %lld\n", + rl_count > 0 ? rl_time / rl_count : 0, + ru_count > 0 ? ru_time / ru_count : 0, + wl_count > 0 ? wl_time / wl_count : 0, + wu_count > 0 ? wu_time / wu_count : 0, + islock_count > 0 ? islock_time / islock_count : 0 + ); + #if DEBUG + printf( "Avg register_thread: %lld\n" + "Avg unregister_thread: %lld\n", + reg_count > 0 ? reg_time / reg_count : 0, + unreg_count > 0 ? unreg_time / unreg_count : 0 + ); + #endif + #endif +} + +status_t +MultiLocker::InitCheck() +{ + return fInit; +} + +bool +MultiLocker::ReadLock() +{ + #if TIMING + bigtime_t start = system_time(); + #endif + + bool locked = false; + + //the lock must be initialized + if (fInit == B_OK) { + if (IsWriteLocked()) { + //the writer simply increments the nesting + fWriterNest++; + locked = true; + } else { + //increment and retrieve the current count of readers + int32 current_count = atomic_add(&fReadCount, 1); + if (current_count < 0) { + //a writer holds the lock so wait for fReadSem to be released + locked = (acquire_sem_etc(fReadSem, 1, B_DO_NOT_RESCHEDULE, + B_INFINITE_TIMEOUT) == B_OK); + } else locked = true; + #if DEBUG + //register if we acquired the lock + if (locked) register_thread(); + #endif + } + + } + + #if TIMING + bigtime_t end = system_time(); + rl_time += (end - start); + rl_count++; + #endif + + return locked; +} + +bool +MultiLocker::WriteLock() +{ + #if TIMING + bigtime_t start = system_time(); + #endif + + bool locked = false; + + if (fInit == B_OK) { + uint32 stack_base = 0; + thread_id thread = -1; + + if (IsWriteLocked(&stack_base, &thread)) { + //already the writer - increment the nesting count + fWriterNest++; + locked = true; + } else { + //new writer acquiring the lock + if (atomic_add(&fLockCount, 1) >= 1) { + //another writer in the lock - acquire the semaphore + locked = (acquire_sem_etc(fWriterLock, 1, B_DO_NOT_RESCHEDULE, + B_INFINITE_TIMEOUT) == B_OK); + } else locked = true; + + if (locked) { + //new holder of the lock + + //decrement fReadCount by a very large number + //this will cause new readers to block on fReadSem + int32 readers = atomic_add(&fReadCount, -LARGE_NUMBER); + + if (readers > 0) { + //readers hold the lock - acquire fWriteSem + locked = (acquire_sem_etc(fWriteSem, readers, B_DO_NOT_RESCHEDULE, + B_INFINITE_TIMEOUT) == B_OK); + } + if (locked) { + ASSERT(fWriterThread == -1); + //record thread information + fWriterThread = thread; + fWriterStackBase = stack_base; + } + } + } + } + + #if TIMING + bigtime_t end = system_time(); + wl_time += (end - start); + wl_count++; + #endif + + return locked; +} + +bool +MultiLocker::ReadUnlock() +{ + #if TIMING + bigtime_t start = system_time(); + #endif + + bool unlocked = false; + + if (IsWriteLocked()) { + //writers simply decrement the nesting count + fWriterNest--; + unlocked = true; + } else { + //decrement and retrieve the read counter + int32 current_count = atomic_add(&fReadCount, -1); + if (current_count < 0) { + //a writer is waiting for the lock so release fWriteSem + unlocked = (release_sem_etc(fWriteSem, 1, + B_DO_NOT_RESCHEDULE) == B_OK); + } else unlocked = true; + + #ifdef DEBUG + //unregister if we released the lock + if (unlocked) unregister_thread(); + #endif + } + + #if TIMING + bigtime_t end = system_time(); + ru_time += (end - start); + ru_count++; + #endif + + return unlocked; +} + +bool +MultiLocker::WriteUnlock() +{ + #if TIMING + bigtime_t start = system_time(); + #endif + + bool unlocked = false; + + if (IsWriteLocked()) { + //if this is a nested lock simply decrement the nest count + if (fWriterNest > 0) { + fWriterNest--; + unlocked = true; + } else { + //writer finally unlocking + + //increment fReadCount by a large number + //this will let new readers acquire the read lock + //retrieve the number of current waiters + int32 readersWaiting = atomic_add(&fReadCount, LARGE_NUMBER) + LARGE_NUMBER; + + if (readersWaiting > 0) { + //readers are waiting to acquire the lock + unlocked = (release_sem_etc(fReadSem, readersWaiting, + B_DO_NOT_RESCHEDULE) == B_OK); + } else unlocked = true; + + if (unlocked) { + //clear the information + fWriterThread = -1; + fWriterStackBase = 0; + + //decrement and retrieve the lock count + if (atomic_add(&fLockCount, -1) > 1) { + //other writers are waiting so release fWriterLock + unlocked = (release_sem_etc(fWriterLock, 1, + B_DO_NOT_RESCHEDULE) == B_OK); + } + } + } + + } else debugger("Non-writer attempting to WriteUnlock()\n"); + + #if TIMING + bigtime_t end = system_time(); + wu_time += (end - start); + wu_count++; + #endif + + return unlocked; +} + +/* this function demonstrates a nice method of determining if the current thread */ +/* is the writer or not. The method involves caching the index of the page in memory */ +/* where the thread's stack is located. Each time a new writer acquires the lock, */ +/* its thread_id and stack_page are recorded. IsWriteLocked gets the stack_page of the */ +/* current thread and sees if it is a match. If the stack_page matches you are guaranteed */ +/* to have the matching thread. If the stack page doesn't match the more traditional */ +/* find_thread(NULL) method of matching the thread_ids is used. */ + +/* This technique is very useful when dealing with a lock that is acquired in a nested fashion. */ +/* It could be expanded to cache the information of the last thread in the lock, and then if */ +/* the same thread returns while there is no one in the lock, it could save some time, if the */ +/* same thread is likely to acquire the lock again and again. */ +/* I should note another shortcut that could be implemented here */ +/* If fWriterThread is set to -1 then there is no writer in the lock, and we could */ +/* return from this function much faster. However the function is currently set up */ +/* so all of the stack_base and thread_id info is determined here. WriteLock passes */ +/* in some variables so that if the lock is not held it does not have to get the thread_id */ +/* and stack base again. Instead this function returns that information. So this shortcut */ +/* would only move this information gathering outside of this function, and I like it all */ +/* contained. */ + +bool +MultiLocker::IsWriteLocked(uint32 *the_stack_base, thread_id *the_thread) +{ + #if TIMING + bigtime_t start = system_time(); + #endif + + //get a variable on the stack + bool write_lock_holder = false; + + if (fInit == B_OK) { + uint32 stack_base; + thread_id thread = 0; + + //determine which page in memory this stack represents + //this is managed by taking the address of the item on the + //stack and dividing it by the size of the memory pages + //if it is the same as the cached stack_page, there is a match + stack_base = (uint32) &write_lock_holder/B_PAGE_SIZE; + if (fWriterStackBase == stack_base) { + write_lock_holder = true; + } else { + //as there was no stack_page match we resort to the + //tried and true methods + thread = find_thread(NULL); + if (fWriterThread == thread) { + write_lock_holder = true; + } + } + + //if someone wants this information, give it to them + if (the_stack_base != NULL) { + *the_stack_base = stack_base; + } + if (the_thread != NULL) { + *the_thread = thread; + } + } + + #if TIMING + bigtime_t end = system_time(); + islock_time += (end - start); + islock_count++; + #endif + + return write_lock_holder; +} + +bool +MultiLocker::IsReadLocked() +{ + //a properly initialized MultiLocker in non-debug always returns true + bool locked = true; + if (fInit == B_NO_INIT) locked = false; + + #if DEBUG + //determine if the lock is actually held + thread_id thread = find_thread(NULL); + if (fDebugArray[thread % fMaxThreads] > 0) locked = true; + else locked = false; + #endif + + return locked; +} + + +/* these two functions manage the debug array for readers */ +/* an array is created in the constructor large enough to hold */ +/* an int32 for each of the maximum number of threads the system */ +/* can have at one time. */ +/* this array does not need to be locked because each running thread */ +/* can be uniquely mapped to a slot in the array by performing: */ +/* thread_id % max_threads */ +/* each time ReadLock is called while in debug mode the thread_id */ +/* is retrived in register_thread() and the count is adjusted in the */ +/* array. If register thread is ever called and the count is not 0 then */ +/* an illegal, potentially deadlocking nested ReadLock occured */ +/* unregister_thread clears the appropriate slot in the array */ + +/* this system could be expanded or retracted to include multiple arrays of information */ +/* in all fairness for it's current use, fDebugArray could be an array of bools */ + +/* The disadvantage of this system for maintaining state is that it sucks up a ton of */ +/* memory. The other method (which would be slower), would involve an additional lock and */ +/* traversing a list of cached information. As this is only for a debug mode, the extra memory */ +/* was not deemed to be a problem */ + +void +MultiLocker::register_thread() +{ + #ifdef DEBUG + #if TIMING + bigtime_t start = system_time(); + #endif + + thread_id thread = find_thread(NULL); + + ASSERT_WITH_MESSAGE(fDebugArray[thread%fMaxThreads] == 0,"Nested ReadLock!\n"); + fDebugArray[thread%fMaxThreads]++; + + #if TIMING + bigtime_t end = system_time(); + reg_time += (end - start); + reg_count++; + #endif + #else + debugger("register_thread should never be called unless in DEBUG mode!\n"); + #endif +} + +void +MultiLocker::unregister_thread() +{ + #ifdef DEBUG + #if TIMING + bigtime_t start = system_time(); + #endif + + thread_id thread = find_thread(NULL); + + ASSERT(fDebugArray[thread%fMaxThreads] == 1); + fDebugArray[thread%fMaxThreads]--; + + #if TIMING + bigtime_t end = system_time(); + unreg_time += (end - start); + unreg_count++; + #endif + #else + debugger("unregister_thread should never be called unless in DEBUG mode!\n"); + #endif + +} + + + diff --git a/src/tests/servers/app/newerClipping/MultiLocker.h b/src/tests/servers/app/newerClipping/MultiLocker.h new file mode 100644 index 0000000000..22406dfc32 --- /dev/null +++ b/src/tests/servers/app/newerClipping/MultiLocker.h @@ -0,0 +1,114 @@ +/* MultiLocker.h */ +/* + Copyright 1999, Be Incorporated. All Rights Reserved. + This file may be used under the terms of the Be Sample Code License. +*/ + +/* multiple-reader single-writer locking class */ + +#ifndef MULTI_LOCKER_H +#define MULTI_LOCKER_H + +//#define TIMING 1 + +#include + +const int32 LARGE_NUMBER = 100000; + +class MultiLocker { + public: + MultiLocker(const char* semaphoreBaseName); + virtual ~MultiLocker(); + + status_t InitCheck(); + + //locking for reading or writing + bool ReadLock(); + bool WriteLock(); + + //unlocking after reading or writing + bool ReadUnlock(); + bool WriteUnlock(); + + //does the current thread hold a write lock ? + bool IsWriteLocked(uint32 *stack_base = NULL, thread_id *thread = NULL); + //in DEBUG mode returns whether the lock is held + //in non-debug mode returns true + bool IsReadLocked(); + + private: + //functions for managing the DEBUG reader array + void register_thread(); + void unregister_thread(); + + status_t fInit; + //readers adjust count and block on fReadSem when a writer + //hold the lock + int32 fReadCount; + sem_id fReadSem; + //writers adjust the count and block on fWriteSem + //when readers hold the lock + int32 fWriteCount; + sem_id fWriteSem; + //writers must acquire fWriterLock when acquiring a write lock + int32 fLockCount; + sem_id fWriterLock; + int32 fWriterNest; + + thread_id fWriterThread; + uint32 fWriterStackBase; + + int32 * fDebugArray; + int32 fMaxThreads; + +#if TIMING + uint32 rl_count; + bigtime_t rl_time; + uint32 ru_count; + bigtime_t ru_time; + uint32 wl_count; + bigtime_t wl_time; + uint32 wu_count; + bigtime_t wu_time; + uint32 islock_count; + bigtime_t islock_time; + uint32 reg_count; + bigtime_t reg_time; + uint32 unreg_count; + bigtime_t unreg_time; +#endif +}; + +class AutoWriteLocker { + public: + AutoWriteLocker(MultiLocker* lock) + : fLock(lock) + { + fLock->WriteLock(); + } + ~AutoWriteLocker() + { + fLock->WriteUnlock(); + } + private: + MultiLocker* fLock; +}; + +class AutoReadLocker { + public: + AutoReadLocker(MultiLocker* lock) + : fLock(lock) + { + fLock->ReadLock(); + } + ~AutoReadLocker() + { + fLock->ReadUnlock(); + } + private: + MultiLocker* fLock; +}; + + + +#endif diff --git a/src/tests/servers/app/newerClipping/ViewLayer.cpp b/src/tests/servers/app/newerClipping/ViewLayer.cpp new file mode 100644 index 0000000000..3732d874de --- /dev/null +++ b/src/tests/servers/app/newerClipping/ViewLayer.cpp @@ -0,0 +1,400 @@ + +#include + +#include // for resize modes + +#include "Desktop.h" +#include "DrawingEngine.h" + +#include "ViewLayer.h" + +extern BWindow* wind; + +// constructor +ViewLayer::ViewLayer(BRect frame, const char* name, + uint32 resizeMode, uint32 flags, + rgb_color viewColor) + : fName(name), + + fFrame(frame), + fScrollingOffset(0.0, 0.0), + + fViewColor(viewColor), + + fResizeMode(resizeMode), + fFlags(flags), + fShowLevel(1), + + fWindow(NULL), + fParent(NULL), + + fFirstChild(NULL), + fPreviousSibling(NULL), + fNextSibling(NULL), + fLastChild(NULL), + + fCurrentChild(NULL), + + fLocalClipping(Bounds()), + fScreenClipping(), + fScreenClippingValid(false) +{ +} + +// destructor +ViewLayer::~ViewLayer() +{ + // iterate over children and delete each one + ViewLayer* layer = fFirstChild; + while (layer) { + ViewLayer* toast = layer; + layer = layer->fNextSibling; + delete toast; + } +} + +// Bounds +BRect +ViewLayer::Bounds() const +{ + BRect bounds(fScrollingOffset.x, fScrollingOffset.y, + fScrollingOffset.x + fFrame.Width(), + fScrollingOffset.y + fFrame.Height()); + return bounds; +} + +// AttachedToWindow +void +ViewLayer::AttachedToWindow(WindowLayer* window) +{ + fWindow = window; + for (ViewLayer* child = FirstChild(); child; child = NextChild()) + child->AttachedToWindow(window); +} + + +// DetachedFromWindow +void +ViewLayer::DetachedFromWindow() +{ + fWindow = NULL; + for (ViewLayer* child = FirstChild(); child; child = NextChild()) + child->DetachedFromWindow(); +} + +// AddChild +void +ViewLayer::AddChild(ViewLayer* layer) +{ + if (layer->fParent) { + printf("ViewLayer::AddChild() - ViewLayer already has a parent\n"); + return; + } + + layer->fParent = this; + + if (!fLastChild) { + // no children yet + fFirstChild = layer; + } else { + // append layer to formerly last child + fLastChild->fNextSibling = layer; + layer->fPreviousSibling = fLastChild; + } + fLastChild = layer; + + if (fParent) { + RebuildClipping(false); + } +} + +// RemoveChild +bool +ViewLayer::RemoveChild(ViewLayer* layer) +{ + if (layer->fParent != this) { + printf("ViewLayer::RemoveChild(%p - %s) - ViewLayer is not child of this (%p) layer!\n", layer, layer ? layer->Name() : NULL, this); + return false; + } + + layer->fParent = NULL; + + if (fLastChild == layer) + fLastChild = layer->fPreviousSibling; + // layer->fNextSibling would be NULL + + if (fFirstChild == layer ) + fFirstChild = layer->fNextSibling; + // layer->fPreviousSibling would be NULL + + // connect child before and after layer + if (layer->fPreviousSibling) + layer->fPreviousSibling->fNextSibling = layer->fNextSibling; + + if (layer->fNextSibling) + layer->fNextSibling->fPreviousSibling = layer->fPreviousSibling; + + // layer has no siblings anymore + layer->fPreviousSibling = NULL; + layer->fNextSibling = NULL; + + // TODO: track regions + RebuildClipping(false); + + return true; +} + +// FirstChild +ViewLayer* +ViewLayer::FirstChild() const +{ + fCurrentChild = fFirstChild; + return fCurrentChild; +} + +// PreviousChild +ViewLayer* +ViewLayer::PreviousChild() const +{ + fCurrentChild = fCurrentChild->fPreviousSibling; + return fCurrentChild; +} + +// NextChild +ViewLayer* +ViewLayer::NextChild() const +{ + fCurrentChild = fCurrentChild->fNextSibling; + return fCurrentChild; +} + +// LastChild +ViewLayer* +ViewLayer::LastChild() const +{ + fCurrentChild = fLastChild; + return fCurrentChild; +} + +// TopLayer +ViewLayer* +ViewLayer::TopLayer() +{ + if (fParent) + return fParent->TopLayer(); + + return this; +} + +// CountChildren +uint32 +ViewLayer::CountChildren() const +{ + uint32 count = 0; + if (ViewLayer* layer = fFirstChild) { + count++; + while (layer->fNextSibling) { + count++; + layer = layer->fNextSibling; + } + } + return count; +} + +// ConvertToTop +void +ViewLayer::ConvertToTop(BPoint* point) const +{ + // remove scrolling offset and convert to parent coordinate space + point->x += fFrame.left - fScrollingOffset.x; + point->y += fFrame.top - fScrollingOffset.y; + + if (fParent) + fParent->ConvertToTop(point); +} + +// ConvertToTop +void +ViewLayer::ConvertToTop(BRect* rect) const +{ + // remove scrolling offset and convert to parent coordinate space + rect->OffsetBy(fFrame.left - fScrollingOffset.x, + fFrame.top - fScrollingOffset.y); + + if (fParent) + fParent->ConvertToTop(rect); +} + +// ConvertToTop +void +ViewLayer::ConvertToTop(BRegion* region) const +{ + // remove scrolling offset and convert to parent coordinate space + region->OffsetBy(fFrame.left - fScrollingOffset.x, + fFrame.top - fScrollingOffset.y); + + if (fParent) + fParent->ConvertToTop(region); +} + +// SetName +void +ViewLayer::SetName(const char* string) +{ + fName.SetTo(string); +} + +// MoveBy +void +ViewLayer::MoveBy(int32 x, int32 y) +{ + fFrame.OffsetBy(x, y); + + _InvalidateScreenClipping(true); + // TODO: ... +} + +// ResizeBy +void +ViewLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion) +{ + BRect oldBounds(Bounds()); + + fFrame.right += x; + fFrame.bottom += y; + + // TODO: broken when getting smaller + // ... either here or in WindowLayer::ResizeBy() + + BRegion dirty(Bounds()); + dirty.Exclude(oldBounds); + if (dirty.CountRects() > 0) { + ConvertToTop(&dirty); + dirtyRegion->Include(&dirty); + } + + RebuildClipping(false); + _InvalidateScreenClipping(false); + // TODO: layout children + // TODO: ... +} + +// ScrollBy +void +ViewLayer::ScrollBy(int32 x, int32 y) +{ + fScrollingOffset.x += x; + fScrollingOffset.y += y; + // TODO: CopyRegion... + // TODO: ... +} + +// Draw +void +ViewLayer::Draw(DrawingEngine* drawingEngine, BRegion* effectiveClipping, bool deep) +{ + if (drawingEngine->Lock()) { + // TODO intersect with local dirtyRegion + // fill visible region with view color + drawingEngine->SetHighColor(fViewColor); + drawingEngine->FillRegion(effectiveClipping); + + drawingEngine->MarkDirty(effectiveClipping); + drawingEngine->Unlock(); + + // let children draw + if (deep) { + for (ViewLayer* child = FirstChild(); child; child = NextChild()) { + child->Draw(drawingEngine, effectiveClipping, deep); + } + } + } +} + +// IsHidden +bool +ViewLayer::IsHidden() const +{ + // if we're explicitely hidden, then we're hidden... + if (fShowLevel < 1) + return true; + + // ...but if we're not hidden, we might still be hidden if our parent is + if (fParent) + return fParent->IsHidden(); + + // nope, definitely not hidden + return false; +} + +// Hide +void +ViewLayer::Hide() +{ + fShowLevel--; + + // TODO: track regions +} + +// Show +void +ViewLayer::Show() +{ + fShowLevel++; + + // TODO: track regions +} + +// PrintToStream +void +ViewLayer::PrintToStream() const +{ +} + +// RebuildClipping +void +ViewLayer::RebuildClipping(bool deep) +{ + // remember current local clipping in dirty region + BRegion oldLocalClipping(fLocalClipping); + + // the clipping spans over the bounds area + fLocalClipping.Set(Bounds()); + + // exclude all childs from the clipping + for (ViewLayer* child = FirstChild(); child; child = NextChild()) { + fLocalClipping.Exclude(child->Frame()); + + if (deep) + child->RebuildClipping(deep); + } + + fScreenClippingValid = false; +} + +// ScreenClipping +BRegion& +ViewLayer::ScreenClipping() const +{ + if (!fScreenClippingValid) { + fScreenClipping = fLocalClipping; + ConvertToTop(&fScreenClipping); + fScreenClippingValid = true; + } + return fScreenClipping; +} + +// _InvalidateScreenClipping +void +ViewLayer::_InvalidateScreenClipping(bool deep) +{ + fScreenClippingValid = false; + if (deep) { + // invalidate the childrens screen clipping as well + for (ViewLayer* child = FirstChild(); child; child = NextChild()) { + child->_InvalidateScreenClipping(deep); + } + } + +} + diff --git a/src/tests/servers/app/newerClipping/ViewLayer.h b/src/tests/servers/app/newerClipping/ViewLayer.h new file mode 100644 index 0000000000..80ef09b797 --- /dev/null +++ b/src/tests/servers/app/newerClipping/ViewLayer.h @@ -0,0 +1,111 @@ + +#ifndef VIEW_LAYER_H +#define VIEW_LAYER_H + +#include +#include + + +class DrawingEngine; +class WindowLayer; + +class ViewLayer { + public: + ViewLayer(BRect frame, + const char* name, + uint32 reizeMode, + uint32 flags, + rgb_color viewColor); + + virtual ~ViewLayer(); + + inline BRect Frame() const + { return fFrame; } + BRect Bounds() const; + + inline rgb_color ViewColor() const + { return fViewColor; } + + void AttachedToWindow(WindowLayer* window); + void DetachedFromWindow(); + + // tree stuff + void AddChild(ViewLayer* layer); + bool RemoveChild(ViewLayer* layer); + + inline ViewLayer* Parent() const + { return fParent; } + + ViewLayer* FirstChild() const; + ViewLayer* PreviousChild() const; + ViewLayer* NextChild() const; + ViewLayer* LastChild() const; + + ViewLayer* TopLayer(); + + uint32 CountChildren() const; + + // coordinate conversion + void ConvertToTop(BPoint* point) const; + void ConvertToTop(BRect* rect) const; + void ConvertToTop(BRegion* region) const; + + // settings + void SetName(const char* string); + inline const char* Name() const + { return fName.String(); } + + void MoveBy(int32 dx, int32 dy); + void ResizeBy(int32 dx, int32 dy, BRegion* dirtyRegion); + void ScrollBy(int32 dx, int32 dy); + + void Draw(DrawingEngine* drawingEngine, + BRegion* effectiveClipping, + bool deep = false); + + bool IsHidden() const; + void Hide(); + void Show(); + + // clipping + void RebuildClipping(bool deep = false); + BRegion& ScreenClipping() const; + + // debugging + void PrintToStream() const; + +private: + void _InvalidateScreenClipping(bool deep = false); + + BString fName; + // area within parent coordinate space + BRect fFrame; + // scrolling offset + BPoint fScrollingOffset; + + rgb_color fViewColor; + + uint32 fResizeMode; + uint32 fFlags; + int32 fShowLevel; + + WindowLayer* fWindow; + ViewLayer* fParent; + + ViewLayer* fFirstChild; + ViewLayer* fPreviousSibling; + ViewLayer* fNextSibling; + ViewLayer* fLastChild; + + // used for traversing the childs + mutable ViewLayer* fCurrentChild; + + // clipping + BRegion fLocalClipping; + + mutable BRegion fScreenClipping; + mutable bool fScreenClippingValid; + +}; + +#endif // LAYER_H diff --git a/src/tests/servers/app/newerClipping/WindowLayer.cpp b/src/tests/servers/app/newerClipping/WindowLayer.cpp new file mode 100644 index 0000000000..48ef223dbb --- /dev/null +++ b/src/tests/servers/app/newerClipping/WindowLayer.cpp @@ -0,0 +1,219 @@ + +#include +#include + +#include +#include + +#include "Desktop.h" +#include "DrawingEngine.h" + +#include "WindowLayer.h" + +#define SLOW_DRAWING 0 + +// constructor +WindowLayer::WindowLayer(BRect frame, const char* name, + DrawingEngine* drawingEngine, Desktop* desktop) + : BLooper(name), + fFrame(frame), + fVisibleRegion(), + + fBorderColor((rgb_color){ 255, 203, 0, 255 }), + + fTopLayer(NULL), + + fDrawingEngine(drawingEngine), + fDesktop(desktop) +{ + // the top layer is special, it has a coordinate system + // as if it was attached directly to the desktop, therefor, + // the coordinate conversion through the layer tree works + // as expected, since the top layer has no "parent" but has + // fFrame as if it had + fTopLayer = new(nothrow) ViewLayer(fFrame, "top view", B_FOLLOW_ALL, 0, + (rgb_color){ 255, 255, 255, 255 }); +} + +// destructor +WindowLayer::~WindowLayer() +{ + delete fTopLayer; +} + +// MessageReceived +void +WindowLayer::MessageReceived(BMessage* message) +{ + switch (message->what) { + case MSG_REDRAW: { + if (!MessageQueue()->FindMessage(MSG_REDRAW, 0)) { + while (!fDesktop->ReadLockClipping()) { +//printf("%s MSG_REDRAW -> timeout\n", Name()); + if (MessageQueue()->FindMessage(MSG_REDRAW, 0)) { +//printf("%s MSG_REDRAW -> timeout - leaving because there are pending redraws\n", Name()); + return; + } + } + _DrawContents(fTopLayer); + _DrawBorder(); + fDesktop->ReadUnlockClipping(); + } else { +//printf("%s MSG_REDRAW -> pending redraws\n", Name()); + } + break; + } + default: + BLooper::MessageReceived(message); + break; + } +} + +// SetClipping +void +WindowLayer::SetClipping(BRegion* stillAvailableOnScreen) +{ + // start from full region (as if the window was fully visible) + GetFullRegion(&fVisibleRegion); + // clip to region still available on screen + fVisibleRegion.IntersectWith(stillAvailableOnScreen); +} + +// GetFullRegion +void +WindowLayer::GetFullRegion(BRegion* region) const +{ + // start from the frame, extend to include decorator border + region->Set(BRect(fFrame.left - 4, fFrame.top - 4, + fFrame.right + 4, fFrame.bottom + 4)); + // add the title tab + region->Include(BRect(fFrame.left - 4, fFrame.top - 20, + (fFrame.left + fFrame.right) / 2, fFrame.top - 5)); +} + +// GetBorderRegion +void +WindowLayer::GetBorderRegion(BRegion* region) const +{ + // TODO: speed up by avoiding "Exclude()" + // start from the frame, extend to include decorator border + region->Set(BRect(fFrame.left - 4, fFrame.top - 4, + fFrame.right + 4, fFrame.bottom + 4)); + + region->Exclude(fFrame); + + // add the title tab + region->Include(BRect(fFrame.left - 4, fFrame.top - 20, + (fFrame.left + fFrame.right) / 2, fFrame.top - 5)); + + // resize handle + // if (B_DOCUMENT_WINDOW_LOOK) + region->Include(BRect(fFrame.right - 10, fFrame.bottom - 10, + fFrame.right, fFrame.bottom)); +} + +// MoveBy +void +WindowLayer::MoveBy(int32 x, int32 y) +{ + if (x == 0 && y == 0) + return; + + fFrame.OffsetBy(x, y); + + fTopLayer->MoveBy(x, y); + + // TODO: move a local dirty region! +} + +// ResizeBy +void +WindowLayer::ResizeBy(int32 x, int32 y, BRegion* dirtyRegion) +{ + if (x == 0 && y == 0) + return; + +// BRegion previousBorder(); +// + fFrame.right += x; + fFrame.bottom += y; + + // the border is dirty, put it into + // dirtyRegion for a start + GetBorderRegion(dirtyRegion); + + fTopLayer->ResizeBy(x, y, dirtyRegion); +} + +// AddChild +void +WindowLayer::AddChild(ViewLayer* layer) +{ + fTopLayer->AddChild(layer); + + // TODO: trigger redraw for dirty regions +} + +// MarkDirty +void +WindowLayer::MarkDirty(BRegion* regionOnScreen) +{ + fDesktop->MarkDirty(regionOnScreen); +} + +# pragma mark - + +// _DrawContents +void +WindowLayer::_DrawContents(ViewLayer* layer) +{ +//printf("%s - DrawContents()\n", Name()); +#if SLOW_DRAWING + snooze(10000); +#endif + + if (!layer) + layer = fTopLayer; + + BRegion effectiveLayerClipping(layer->ScreenClipping()); + effectiveLayerClipping.IntersectWith(&fVisibleRegion); + effectiveLayerClipping.IntersectWith(fDesktop->DirtyRegion()); + if (effectiveLayerClipping.Frame().IsValid()) { + layer->Draw(fDrawingEngine, &effectiveLayerClipping, true); + fDesktop->MarkClean(&effectiveLayerClipping); + } +//else { +//printf(" nothing to do\n"); +//} + +} + +// _DrawBorder +void +WindowLayer::_DrawBorder() +{ +//printf("%s - DrawBorder()\n", Name()); +#if SLOW_DRAWING + snooze(10000); +#endif + + // construct the region containing just the border + BRegion borderRegion(fVisibleRegion); + borderRegion.Exclude(fFrame); + // intersect with the Desktop's dirty region + borderRegion.IntersectWith(fDesktop->DirtyRegion()); + + if (borderRegion.Frame().IsValid()) { + if (fDrawingEngine->Lock()) { + fDrawingEngine->SetHighColor(fBorderColor); + fDrawingEngine->FillRegion(&borderRegion); + fDrawingEngine->MarkDirty(&borderRegion); + fDrawingEngine->Unlock(); + } + fDesktop->MarkClean(&borderRegion); + } +//else { +//printf(" nothing to do\n"); +//} +} + diff --git a/src/tests/servers/app/newerClipping/WindowLayer.h b/src/tests/servers/app/newerClipping/WindowLayer.h new file mode 100644 index 0000000000..9d9414902f --- /dev/null +++ b/src/tests/servers/app/newerClipping/WindowLayer.h @@ -0,0 +1,61 @@ + +#ifndef WINDOW_LAYER_H +#define WINDOW_LAYER_H + +#include +#include +#include + +#include "ViewLayer.h" + +class Desktop; +class DrawingEngine; + +enum { + MSG_REDRAW = 'rdrw', +}; + +class WindowLayer : public BLooper { + public: + WindowLayer(BRect frame, const char* name, + DrawingEngine* drawingEngine, + Desktop* desktop); + virtual ~WindowLayer(); + + virtual void MessageReceived(BMessage* message); + + inline BRect Frame() const + { return fFrame; } + void SetClipping(BRegion* stillAvailableOnScreen); + inline BRegion& VisibleRegion() + { return fVisibleRegion; } + void GetFullRegion(BRegion* region) const; + void GetBorderRegion(BRegion* region) const; + + void MoveBy(int32 x, int32 y); + void ResizeBy(int32 x, int32 y, BRegion* dirtyRegion); + + void AddChild(ViewLayer* layer); + + void MarkDirty(BRegion* regionOnScreen); + + private: + void _DrawContents(ViewLayer* layer = NULL); + void _DrawBorder(); + + + BRect fFrame; + // the visible region is only recalculated from the + // Desktop thread, when using it, Desktop::LockClipping() + // has to be called + BRegion fVisibleRegion; + + rgb_color fBorderColor; + + ViewLayer* fTopLayer; + + DrawingEngine* fDrawingEngine; + Desktop* fDesktop; +}; + +#endif // WINDOW_LAYER_H diff --git a/src/tests/servers/app/newerClipping/main.cpp b/src/tests/servers/app/newerClipping/main.cpp new file mode 100644 index 0000000000..685cc55e92 --- /dev/null +++ b/src/tests/servers/app/newerClipping/main.cpp @@ -0,0 +1,108 @@ + +#include +#include +#include + +#include +#include + +#include "Desktop.h" +#include "DrawingEngine.h" +#include "ViewLayer.h" +#include "WindowLayer.h" + +class App : public BApplication { + public: + App(); + ~App(); + + virtual void ReadyToRun(); +}; + +class Window : public BWindow { + public: + Window(const char* title); + ~Window(); + + void AddWindow(BRect frame, const char* name); + void Test(); + private: + DrawView* fView; + Desktop* fDesktop; +}; + +App::App() + : BApplication("application/x-vnd.stippi.ClippingTest") +{ + srand(real_time_clock_usecs()); +} + +App::~App() +{ +} + +void +App::ReadyToRun() +{ + Window* win = new Window("clipping"); + win->Show(); + + win->Test(); +} + +Window::Window(const char* title) + : BWindow(BRect(50, 50, 800, 650), title, + B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, + B_QUIT_ON_WINDOW_CLOSE | B_ASYNCHRONOUS_CONTROLS) +{ + fView = new DrawView(Bounds()); + fDesktop = new Desktop(fView); + fDesktop->Run(); + AddChild(fView); + fView->MakeFocus(true); +} + +Window::~Window() +{ + fDesktop->Lock(); + fDesktop->Quit(); +} + +// AddWindow +void +Window::AddWindow(BRect frame, const char* name) +{ + WindowLayer* window = new WindowLayer(frame, name, + fDesktop->GetDrawingEngine(), + fDesktop); + window->Run(); + + BMessage message(MSG_ADD_WINDOW); + message.AddPointer("window", (void*)window); + fDesktop->PostMessage(&message); +} + +// Test +void +Window::Test() +{ + AddWindow(BRect(20, 20, 80, 80), "Window 1"); + AddWindow(BRect(60, 60, 220, 180), "Window 2"); + AddWindow(BRect(120, 160, 500, 380), "Window 3"); + AddWindow(BRect(40, 210, 400, 280), "Window 4"); + AddWindow(BRect(180, 410, 400, 680), "Window 5"); + AddWindow(BRect(30, 350, 100, 440), "Window 6"); + AddWindow(BRect(80, 10, 200, 120), "Window 7"); + AddWindow(BRect(480, 40, 670, 320), "Window 8"); + AddWindow(BRect(250, 500, 310, 600), "Window 9"); + AddWindow(BRect(130, 450, 230, 500), "Window 10"); +} + +// main +int +main(int argc, const char* argv[]) +{ + App app; + app.Run(); + return 0; +} diff --git a/src/tests/servers/app/newerClipping/makefile b/src/tests/servers/app/newerClipping/makefile new file mode 100644 index 0000000000..ccfd41ca82 --- /dev/null +++ b/src/tests/servers/app/newerClipping/makefile @@ -0,0 +1,125 @@ +## BeOS Generic Makefile v2.2 ## + +## Fill in this file to specify the project being created, and the referenced +## makefile-engine will do all of the hard work for you. This handles both +## Intel and PowerPC builds of the BeOS. + +## Application Specific Settings --------------------------------------------- + +# specify the name of the binary +NAME= ../ClippingTest + +# specify the type of binary +# APP: Application +# SHARED: Shared library or add-on +# STATIC: Static library archive +# DRIVER: Kernel Driver +TYPE= APP + +# add support for new Pe and Eddie features +# to fill in generic makefile + +#%{ +# @src->@ + +# specify the source files to use +# full paths or paths relative to the makefile can be included +# all files, regardless of directory, will have their object +# files created in the common object directory. +# Note that this means this makefile will not work correctly +# if two source files with the same name (source.c or source.cpp) +# are included from different directories. Also note that spaces +# in folder names do not work well with this makefile. +SRCS= Desktop.cpp \ + DrawingEngine.cpp \ + main.cpp \ + MultiLocker.cpp \ + ViewLayer.cpp \ + WindowLayer.cpp + +# specify the resource files to use +# full path or a relative path to the resource file can be used. +RSRCS= + +# @<-src@ +#%} + +# end support for Pe and Eddie + +# specify additional libraries to link against +# there are two acceptable forms of library specifications +# - if your library follows the naming pattern of: +# libXXX.so or libXXX.a you can simply specify XXX +# library: libbe.so entry: be +# +# - if your library does not follow the standard library +# naming scheme you need to specify the path to the library +# and it's name +# library: my_lib.a entry: my_lib.a or path/my_lib.a +LIBS= be + +# specify additional paths to directories following the standard +# libXXX.so or libXXX.a naming scheme. You can specify full paths +# or paths relative to the makefile. The paths included may not +# be recursive, so include all of the paths where libraries can +# be found. Directories where source files are found are +# automatically included. +LIBPATHS= + +# additional paths to look for system headers +# thes use the form: #include
+# source file directories are NOT auto-included here +SYSTEM_INCLUDE_PATHS = + +# additional paths to look for local headers +# thes use the form: #include "header" +# source file directories are automatically included +LOCAL_INCLUDE_PATHS = + +# specify the level of optimization that you desire +# NONE, SOME, FULL +OPTIMIZE= SOME + +# specify any preprocessor symbols to be defined. The symbols will not +# have their values set automatically; you must supply the value (if any) +# to use. For example, setting DEFINES to "DEBUG=1" will cause the +# compiler option "-DDEBUG=1" to be used. Setting DEFINES to "DEBUG" +# would pass "-DDEBUG" on the compiler's command line. +DEFINES= + +# specify special warning levels +# if unspecified default warnings will be used +# NONE = supress all warnings +# ALL = enable all warnings +WARNINGS = + +# specify whether image symbols will be created +# so that stack crawls in the debugger are meaningful +# if TRUE symbols will be created +SYMBOLS = + +# specify debug settings +# if TRUE will allow application to be run from a source-level +# debugger. Note that this will disable all optimzation. +DEBUGGER = + +# specify additional compiler flags for all files +COMPILER_FLAGS = + +# specify additional linker flags +LINKER_FLAGS = + +# specify the version of this particular item +# (for example, -app 3 4 0 d 0 -short 340 -long "340 "`echo -n -e '\302\251'`"1999 GNU GPL") +# This may also be specified in a resource. +APP_VERSION = + +# (for TYPE == DRIVER only) Specify desired location of driver in the /dev +# hierarchy. Used by the driverinstall rule. E.g., DRIVER_PATH = video/usb will +# instruct the driverinstall rule to place a symlink to your driver's binary in +# ~/add-ons/kernel/drivers/dev/video/usb, so that your driver will appear at +# /dev/video/usb when loaded. Default is "misc". +DRIVER_PATH = + +## include the makefile-engine +include $(BUILDHOME)/etc/makefile-engine