From cb55ef9fb597a3f3bc91e53017360b18b20dd27b Mon Sep 17 00:00:00 2001 From: John Scipione Date: Mon, 27 Jun 2011 22:24:55 -0400 Subject: [PATCH] WIP: Created a BScrollMenu class that works like BMenuWindow but works on a view instead of a window. Use this to implement a scrollable BarMenuBar in Deskbar. The basics work but there are issues still with sizing and other issues. Modify the ScrollMenu class to use the layout kit by adding a constructor that doesn't take a view. Get the BScrollMenu class to follow the size of the BMenu it is a parent of. Adjust the scrollers to appear in the right places. This is a WIP but it works in Deskbar, next step is to integrate this directly into BMenu with the scrollers as children of the menu instead of as children of the BScroller class. Rebase changes on top of master Deskbar scrolling works for the most part, just need to fix the bottom arrow and clean up a bit. --- headers/os/interface/ScrollMenu.h | 55 ++++ src/apps/deskbar/BarView.cpp | 36 ++- src/apps/deskbar/BarView.h | 3 +- src/apps/deskbar/ExpandoMenuBar.cpp | 19 +- src/apps/deskbar/TeamMenuItem.cpp | 24 +- src/kits/interface/Jamfile | 1 + src/kits/interface/Menu.cpp | 12 +- src/kits/interface/MenuBar.cpp | 32 +- src/kits/interface/ScrollMenu.cpp | 436 ++++++++++++++++++++++++++++ 9 files changed, 580 insertions(+), 38 deletions(-) create mode 100644 headers/os/interface/ScrollMenu.h create mode 100644 src/kits/interface/ScrollMenu.cpp diff --git a/headers/os/interface/ScrollMenu.h b/headers/os/interface/ScrollMenu.h new file mode 100644 index 0000000000..4bb14f2981 --- /dev/null +++ b/headers/os/interface/ScrollMenu.h @@ -0,0 +1,55 @@ +/* + * Copyright 2011, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Marc Flerackers (mflerackers@androme.be) + * Stefano Ceccherini (stefano.ceccherini@gmail.com) + * John Scipione (jscipione@gmail.com) + */ +#ifndef SCROLL_MENU_H +#define SCROLL_MENU_H + + +#include + +class BLayout; +class BMenu; +class BMenuScroller; + + +class BScrollMenu : public BView { +public: + BScrollMenu(BMenu* menu); + virtual ~BScrollMenu(); + + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + virtual void Draw(BRect updateRect); + virtual void FrameResized(float newWidth, float newHeight); + + void AttachScrollers(); + void DetachScrollers(); + bool HasScrollers() const; + + void SetSmallStep(float step); + void GetSteps(float* _smallStep, float* _largeStep) const; + bool CheckForScrolling(const BPoint& cursor); + bool TryScrollBy(const float& step); + +protected: + bool _Scroll(const BPoint& cursor); + void _ScrollBy(const float& step); + +private: + BMenu* fMenu; + BMenuScroller* fUpperScroller; + BMenuScroller* fLowerScroller; + + float fScrollStep; + float fValue; + float fLimit; +}; + + +#endif // SCROLL_MENU_H diff --git a/src/apps/deskbar/BarView.cpp b/src/apps/deskbar/BarView.cpp index 0bc16d2f6f..f874bd1536 100644 --- a/src/apps/deskbar/BarView.cpp +++ b/src/apps/deskbar/BarView.cpp @@ -48,6 +48,7 @@ All rights reserved. #include #include #include +#include #include #include "icons.h" @@ -130,6 +131,7 @@ BarViewMessageFilter::Filter(BMessage* message, BHandler** target) TBarView::TBarView(BRect frame, bool vertical, bool left, bool top, uint32 state, float) : BView(frame, "BarView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW), + fBarScrollMenu(NULL), fBarMenuBar(NULL), fExpando(NULL), fTrayLocation(1), @@ -347,8 +349,7 @@ TBarView::MouseDown(BPoint where) void TBarView::PlaceDeskbarMenu() { - // top or bottom, full - if (!fVertical && fBarMenuBar) { + if (!fVertical && fBarMenuBar != NULL) { fBarMenuBar->RemoveSelf(); delete fBarMenuBar; fBarMenuBar = NULL; @@ -367,12 +368,13 @@ TBarView::PlaceDeskbarMenu() // if there isn't a bemenu at this point, // DB should be in top/bottom mode, else error - if (!fBarMenuBar) + if (fBarMenuBar == NULL) return; float width = sMinimumWindowWidth; BPoint loc(B_ORIGIN); BRect menuFrame(fBarMenuBar->Frame()); + if (fState == kFullState) { fBarMenuBar->RemoveTeamMenu(); // TODO: Magic constants need explanation @@ -441,22 +443,22 @@ TBarView::PlaceTray(bool vertSwap, bool leftSwap) void TBarView::PlaceApplicationBar() { - if (fExpando != NULL) { - SaveExpandedItems(); + if (fBarScrollMenu != NULL) { + fBarScrollMenu->RemoveSelf(); + delete fBarScrollMenu; + // Also deletes fExpando + fBarScrollMenu = NULL; + fExpando = NULL; + } else if (fExpando != NULL) { fExpando->RemoveSelf(); delete fExpando; fExpando = NULL; } - BRect screenFrame = (BScreen(Window())).Frame(); - if (fState == kMiniState) { - SizeWindow(screenFrame); - PositionWindow(screenFrame); - Window()->UpdateIfNeeded(); - Invalidate(); + if (fState == kMiniState) return; - } + BRect screenFrame = (BScreen(Window())).Frame(); BRect expandoFrame(0, 0, 0, 0); if (fVertical) { // top left/right @@ -485,7 +487,13 @@ TBarView::PlaceApplicationBar() fExpando = new TExpandoMenuBar(this, expandoFrame, "ExpandoMenuBar", fVertical, !hideLabels && fState != kFullState); - AddChild(fExpando); + + if (fVertical) { + fBarScrollMenu = new BScrollMenu(fExpando); + AddChild(fBarScrollMenu); +//printf("fExpando bottom: %f, fBarScrollMenu bottom: %f\n", fExpando->Frame().bottom, fBarScrollMenu->Frame().bottom); + } else + AddChild(fExpando); if (fVertical) ExpandItems(); @@ -522,7 +530,7 @@ TBarView::GetPreferredWindowSize(BRect screenFrame, float* width, float* height) } else if (fState == kExpandoState) { if (fVertical) { // top left or right - windowHeight = fExpando->Frame().bottom; + windowHeight = fBarScrollMenu->Frame().bottom; } else { // top or bottom, full fExpando->CheckItemSizes(0); diff --git a/src/apps/deskbar/BarView.h b/src/apps/deskbar/BarView.h index f375c872d4..e8e87ba95b 100644 --- a/src/apps/deskbar/BarView.h +++ b/src/apps/deskbar/BarView.h @@ -65,7 +65,7 @@ const float kStatusHeight = 22.0f; const float kHiddenDimension = 1.0f; const float kMaxPreventHidingDist = 80.0f; - +class BScrollMenu; class BShelf; class TBarMenuBar; class TExpandoMenuBar; @@ -168,6 +168,7 @@ class TBarView : public BView { void ExpandItems(); void _ChangeState(BMessage* message); + BScrollMenu* fBarScrollMenu; TBarMenuBar* fBarMenuBar; TExpandoMenuBar* fExpando; diff --git a/src/apps/deskbar/ExpandoMenuBar.cpp b/src/apps/deskbar/ExpandoMenuBar.cpp index 15d724fd58..f9cfbb86dc 100644 --- a/src/apps/deskbar/ExpandoMenuBar.cpp +++ b/src/apps/deskbar/ExpandoMenuBar.cpp @@ -45,6 +45,7 @@ All rights reserved. #include #include #include +#include #include "icons.h" @@ -797,10 +798,22 @@ TExpandoMenuBar::DrawBackground(BRect) void TExpandoMenuBar::CheckForSizeOverrun() { - BRect screenFrame = (BScreen(Window())).Frame(); + if (!fVertical) { + fIsScrolling = false; + return; + } - fIsScrolling = fVertical ? Window()->Frame().bottom > screenFrame.bottom - : false; + BScrollMenu* scrollMenu = dynamic_cast(Parent()); + if (scrollMenu == NULL) + return; + + BRect screenFrame = (BScreen(Window())).Frame(); + fIsScrolling = Window()->Frame().bottom > screenFrame.bottom; + + if (fIsScrolling) + scrollMenu->AttachScrollers(); + else + scrollMenu->DetachScrollers(); } diff --git a/src/apps/deskbar/TeamMenuItem.cpp b/src/apps/deskbar/TeamMenuItem.cpp index 5b7900f244..35ab7ff65c 100644 --- a/src/apps/deskbar/TeamMenuItem.cpp +++ b/src/apps/deskbar/TeamMenuItem.cpp @@ -479,8 +479,7 @@ TTeamMenuItem::DrawContentLabel() if (Submenu() && fVertical) cachedWidth += 18; - const char* label = Label(); - char* truncLabel = NULL; + BString label(Label()); float max = 0; if (fVertical && static_cast(be_app)->Settings()->superExpando) @@ -492,30 +491,23 @@ TTeamMenuItem::DrawContentLabel() BPoint penloc = menu->PenLocation(); BRect frame = Frame(); float offset = penloc.x - frame.left; - if (cachedWidth + offset > max) { - truncLabel = (char*)malloc(strlen(label) + 4); - if (!truncLabel) - return; - TruncateLabel(max-offset, truncLabel); - label = truncLabel; - } + if (cachedWidth + offset > max) + menu->TruncateString(&label, B_TRUNCATE_MIDDLE, max - offset); } if (!label) - label = Label(); + label = BString(Label()); - TBarView* barview = (static_cast(be_app))->BarView(); - bool canHandle = !barview->Dragging() - || barview->AppCanHandleTypes(Signature()); + TBarView* barView = (static_cast(be_app))->BarView(); + bool canHandle = !barView->Dragging() + || barView->AppCanHandleTypes(Signature()); if (_IsSelected() && IsEnabled() && canHandle) menu->SetLowColor(tint_color(menu->LowColor(), B_HIGHLIGHT_BACKGROUND_TINT)); else menu->SetLowColor(menu->LowColor()); - menu->DrawString(label); - - free(truncLabel); + menu->DrawString(label.String()); } diff --git a/src/kits/interface/Jamfile b/src/kits/interface/Jamfile index 81c59e0b32..4109585a2f 100644 --- a/src/kits/interface/Jamfile +++ b/src/kits/interface/Jamfile @@ -101,6 +101,7 @@ MergeObject interface_kit.o : RegionSupport.cpp Screen.cpp ScrollBar.cpp + ScrollMenu.cpp ScrollView.cpp SeparatorItem.cpp SeparatorView.cpp diff --git a/src/kits/interface/Menu.cpp b/src/kits/interface/Menu.cpp index 6905c15605..cde95056fe 100644 --- a/src/kits/interface/Menu.cpp +++ b/src/kits/interface/Menu.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -2811,13 +2812,18 @@ BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger, void BMenu::_UpdateWindowViewSize(const bool &move) { + if (dynamic_cast(this) != NULL) { + BScrollMenu* scrollMenu = dynamic_cast(Parent()); + if (scrollMenu != NULL) + scrollMenu->ResizeTo(Bounds().Width(), Bounds().Height()); + + return; + } + BMenuWindow* window = static_cast(Window()); if (window == NULL) return; - if (dynamic_cast(this) != NULL) - return; - if (!fResizeToFit) return; diff --git a/src/kits/interface/MenuBar.cpp b/src/kits/interface/MenuBar.cpp index b29f9d5ef5..ee2e978b8f 100644 --- a/src/kits/interface/MenuBar.cpp +++ b/src/kits/interface/MenuBar.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -328,7 +329,36 @@ BMenuBar::Draw(BRect updateRect) void BMenuBar::MessageReceived(BMessage* msg) { - BMenu::MessageReceived(msg); + switch (msg->what) { + case B_MOUSE_WHEEL_CHANGED: + { + float deltaY = 0; + msg->FindFloat("be:wheel_delta_y", &deltaY); + if (deltaY == 0) + return; + + BScrollMenu* scrollMenu = dynamic_cast(Parent()); + if (scrollMenu == NULL) + return; + + float largeStep; + float smallStep; + scrollMenu->GetSteps(&smallStep, &largeStep); + + // pressing the option/command/control key scrolls faster + if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY)) + deltaY *= largeStep; + else + deltaY *= smallStep; + + scrollMenu->TryScrollBy(deltaY); + break; + } + + default: + BMenu::MessageReceived(msg); + break; + } } diff --git a/src/kits/interface/ScrollMenu.cpp b/src/kits/interface/ScrollMenu.cpp new file mode 100644 index 0000000000..2923f01dc7 --- /dev/null +++ b/src/kits/interface/ScrollMenu.cpp @@ -0,0 +1,436 @@ +/* + * Copyright 2011, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Marc Flerackers (mflerackers@androme.be) + * Stefano Ceccherini (stefano.ceccherini@gmail.com) + * John Scipione (jscipione@gmail.com) + */ + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +const char* kEmptyMenuLabel = ""; +const int kDefaultScrollStep = 19; +const int kScrollerHeight = 12; + + +class BMenuScroller : public BView { +public: + BMenuScroller(BRect frame); + virtual ~BMenuScroller(); + + bool IsEnabled() const { return fEnabled; }; + void SetEnabled(bool enabled); + +private: + bool fEnabled; +}; + + +class BMenuUpScroller : public BMenuScroller { +public: + BMenuUpScroller(BRect frame); + virtual ~BMenuUpScroller(); + + virtual void Draw(BRect updateRect); +}; + + +class BMenuDownScroller : public BMenuScroller { +public: + BMenuDownScroller(BRect frame); + virtual ~BMenuDownScroller(); + + virtual void Draw(BRect updateRect); +}; + + +// #pragma mark - + + +BMenuScroller::BMenuScroller(BRect frame) + : + BView(frame, "menu scroll arrow", 0, B_WILL_DRAW | B_FRAME_EVENTS), + fEnabled(false) +{ + SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); +} + + +BMenuScroller::~BMenuScroller() +{ +} + + +void +BMenuScroller::SetEnabled(bool enabled) +{ + fEnabled = enabled; + Invalidate(); +} + + +// #pragma mark - + + +BMenuUpScroller::BMenuUpScroller(BRect frame) + : + BMenuScroller(frame) +{ +} + + +BMenuUpScroller::~BMenuUpScroller() +{ +} + + +void +BMenuUpScroller::Draw(BRect updateRect) +{ + SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); + + // Draw the upper arrow. + if (IsEnabled()) + SetHighColor(0, 0, 0); + else { + SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), + B_DARKEN_2_TINT)); + } + + FillRect(Bounds(), B_SOLID_LOW); + + float middle = Bounds().right / 2; + FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3), + BPoint(middle + 5, (kScrollerHeight / 2) + 2), + BPoint(middle - 5, (kScrollerHeight / 2) + 2)); +} + + +// #pragma mark - + + +BMenuDownScroller::BMenuDownScroller(BRect frame) + : + BMenuScroller(frame) +{ +} + + +BMenuDownScroller::~BMenuDownScroller() +{ +} + + +void +BMenuDownScroller::Draw(BRect updateRect) +{ + SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); + + // Draw the lower arrow. + if (IsEnabled()) + SetHighColor(0, 0, 0); + else { + SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), + B_DARKEN_2_TINT)); + } + + BRect frame = Bounds(); + FillRect(frame, B_SOLID_LOW); + + float middle = Bounds().right / 2; + FillTriangle(BPoint(middle, frame.bottom - (kScrollerHeight / 2) + 3), + BPoint(middle + 5, frame.bottom - (kScrollerHeight / 2) - 2), + BPoint(middle - 5, frame.bottom - (kScrollerHeight / 2) - 2)); +} + + +// #pragma mark - + + +BScrollMenu::BScrollMenu(BMenu *menu) + : + BView("menu scroll view", B_WILL_DRAW | B_FRAME_EVENTS), + fMenu(menu), + fUpperScroller(NULL), + fLowerScroller(NULL), + fScrollStep(kDefaultScrollStep) +{ +} + + +BScrollMenu::~BScrollMenu() +{ + if (fMenu != NULL) { + fMenu->RemoveSelf(); + delete fMenu; + fMenu = NULL; + } + + if (fUpperScroller != NULL) { + fUpperScroller->RemoveSelf(); + delete fUpperScroller; + fUpperScroller = NULL; + } + + if (fLowerScroller != NULL) { + fLowerScroller->RemoveSelf(); + delete fLowerScroller; + fLowerScroller = NULL; + } +} + + +void +BScrollMenu::AttachedToWindow() +{ + BView::AttachedToWindow(); + + if (fMenu == NULL) + return; + + AddChild(fMenu); + + // Move the scroll menu into the right position + MoveTo(fMenu->Frame().LeftTop()); + + BFont font; + fMenu->GetFont(&font); + SetFont(&font); +} + + +void +BScrollMenu::DetachedFromWindow() +{ + BView::DetachedFromWindow(); + + if (fMenu != NULL) + fMenu->RemoveSelf(); + + if (fUpperScroller != NULL) + fUpperScroller->RemoveSelf(); + + if (fLowerScroller != NULL) + fLowerScroller->RemoveSelf(); +} + + +void +BScrollMenu::Draw(BRect updateRect) +{ + if (be_control_look != NULL) + return; + + SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT)); + BRect bounds(Bounds()); + + StrokeLine(BPoint(bounds.right, bounds.top), + BPoint(bounds.right, bounds.bottom - 1)); + StrokeLine(BPoint(bounds.left + 1, bounds.bottom), + BPoint(bounds.right, bounds.bottom)); +} + + +void +BScrollMenu::FrameResized(float newWidth, float newHeight) +{ + BView::FrameResized(newWidth, newHeight); + + if (fMenu != NULL) { + if (HasScrollers()) + fMenu->MoveTo(0, kScrollerHeight); + else + fMenu->MoveTo(0, 0); + } +} + + +void +BScrollMenu::AttachScrollers() +{ + if (fMenu == NULL) + return; + + BRect frame = Bounds(); + BRect screenFrame = (BScreen(Window())).Frame(); + + if (HasScrollers()) { + fLimit = Frame().bottom + 2 * kScrollerHeight - screenFrame.bottom; + return; + } + + fMenu->MakeFocus(true); + + if (fUpperScroller == NULL) { + fUpperScroller = new BMenuUpScroller( + BRect(0, 0, frame.right, kScrollerHeight - 1)); + AddChild(fUpperScroller, fMenu); + } + + if (fLowerScroller == NULL) { + fLowerScroller = new BMenuDownScroller( + BRect(0, frame.bottom - kScrollerHeight + 1, frame.right, + frame.bottom)); + AddChild(fLowerScroller); + } + + fUpperScroller->SetEnabled(false); + fLowerScroller->SetEnabled(true); + + fLimit = Frame().bottom + 2 * kScrollerHeight - screenFrame.bottom; + fValue = 0; +} + + +void +BScrollMenu::DetachScrollers() +{ + if (!HasScrollers()) + return; + + if (fLowerScroller) { + fLowerScroller->RemoveSelf(); + delete fLowerScroller; + fLowerScroller = NULL; + } + + if (fUpperScroller) { + fUpperScroller->RemoveSelf(); + delete fUpperScroller; + fUpperScroller = NULL; + } + + if (fMenu) { + // We don't remember the position where the last scrolling + // ended, so scroll back to the beginning. + fMenu->ScrollTo(0, 0); + // Since the scrollers were removed move back up. + //fMenu->ResizeBy(0, 2 * kScrollerHeight); + //fMenu->MoveBy(0, -kScrollerHeight); + fValue = 0; + } +} + + +bool +BScrollMenu::HasScrollers() const +{ + return fMenu != NULL && fUpperScroller != NULL && fLowerScroller != NULL; +} + + +void +BScrollMenu::SetSmallStep(float step) +{ + fScrollStep = step; +} + + +void +BScrollMenu::GetSteps(float* _smallStep, float* _largeStep) const +{ + if (_smallStep != NULL) + *_smallStep = fScrollStep; + if (_largeStep != NULL) { + if (fMenu != NULL) + *_largeStep = fMenu->Frame().Height() - fScrollStep; + else + *_largeStep = fScrollStep * 2; + } +} + + +bool +BScrollMenu::CheckForScrolling(const BPoint &cursor) +{ + if (!HasScrollers()) + return false; + + return _Scroll(cursor); +} + + +bool +BScrollMenu::TryScrollBy(const float& step) +{ + if (!HasScrollers()) + return false; + + _ScrollBy(step); + return true; +} + + +bool +BScrollMenu::_Scroll(const BPoint& where) +{ + ASSERT((fLowerScroller != NULL)); + ASSERT((fUpperScroller != NULL)); + + const BPoint cursor = ConvertFromScreen(where); + const BRect &lowerFrame = fLowerScroller->Frame(); + const BRect &upperFrame = fUpperScroller->Frame(); + + int32 delta = 0; + if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor)) + delta = 1; + else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor)) + delta = -1; + + if (delta == 0) + return false; + + float smallStep; + GetSteps(&smallStep, NULL); + _ScrollBy(smallStep * delta); + + snooze(5000); + + return true; +} + + +void +BScrollMenu::_ScrollBy(const float& step) +{ + if (step > 0) { + if (fValue == 0) + fUpperScroller->SetEnabled(true); + + if (fValue + step >= fLimit) { + // If we reached the limit, only scroll to the end + fMenu->ScrollBy(0, fLimit - fValue); + fValue = fLimit; + fLowerScroller->SetEnabled(false); + } else { + fMenu->ScrollBy(0, step); + fValue += step; + } + } else if (step < 0) { + if (fValue == fLimit) + fLowerScroller->SetEnabled(true); + + if (fValue + step <= 0) { + fMenu->ScrollBy(0, -fValue); + fValue = 0; + fUpperScroller->SetEnabled(false); + } else { + fMenu->ScrollBy(0, step); + fValue += step; + } + } +}