Last active
July 28, 2026 05:59
-
-
Save rguiscard/e6b25580fe8dbc47414dd2e0c48a228e to your computer and use it in GitHub Desktop.
Cards in scrollview (Haiku OS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <Application.h> | |
| #include <Window.h> | |
| #include <View.h> | |
| #include <ScrollView.h> | |
| #include <TextView.h> | |
| #include <String.h> | |
| #include <MenuBar.h> | |
| #include <Menu.h> | |
| #include <MenuItem.h> | |
| static const char* kAppSignature = "application/x-vnd.Test-CardDemo"; | |
| static const uint32 kMsgAddCard = 'adcd'; | |
| static const uint32 kMsgInsertCard = 'incd'; // New message for Insert | |
| // ============================================================================ | |
| // CardView - A card containing wrapped text | |
| // ============================================================================ | |
| class CardView : public BView { | |
| public: | |
| CardView(const char* name, const char* text, float width); | |
| void SetWidth(float width); | |
| virtual void Draw(BRect updateRect); | |
| private: | |
| BTextView* fTextView; | |
| float fWidth; | |
| static const float kInset; | |
| }; | |
| const float CardView::kInset = 15.0f; | |
| CardView::CardView(const char* name, const char* text, float width) | |
| : | |
| BView(name, B_WILL_DRAW | B_FRAME_EVENTS), | |
| fTextView(NULL), | |
| fWidth(width) | |
| { | |
| SetViewColor(255, 255, 255); | |
| fTextView = new BTextView("text", B_WILL_DRAW); | |
| fTextView->SetText(text); | |
| fTextView->SetViewColor(255, 255, 255); | |
| fTextView->MakeEditable(false); | |
| fTextView->SetWordWrap(true); | |
| fTextView->SetStylable(false); | |
| fTextView->SetFlags(fTextView->Flags() & ~(B_NAVIGABLE | B_FRAME_EVENTS)); | |
| rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR); | |
| fTextView->SetFontAndColor(NULL, 0, &textColor); | |
| AddChild(fTextView); | |
| } | |
| void | |
| CardView::SetWidth(float width) | |
| { | |
| fWidth = width; | |
| float textWidth = fWidth - 2.0f * kInset; | |
| if (textWidth < 10.0f) textWidth = 10.0f; | |
| fTextView->ResizeTo(textWidth, 5000.0f); | |
| float textHeight = fTextView->TextHeight(0, fTextView->TextLength()); | |
| float totalHeight = textHeight + 2.0f * kInset; | |
| ResizeTo(fWidth, totalHeight); | |
| fTextView->MoveTo(kInset, kInset); | |
| fTextView->ResizeTo(textWidth, textHeight); | |
| } | |
| void | |
| CardView::Draw(BRect updateRect) | |
| { | |
| BView::Draw(updateRect); | |
| SetHighColor(200, 200, 200); | |
| StrokeRect(Bounds()); | |
| } | |
| // ============================================================================ | |
| // ContentView - Holds all cards, placed inside a BScrollView | |
| // ============================================================================ | |
| class ContentView : public BView { | |
| public: | |
| ContentView(float initialWidth); | |
| virtual void AttachedToWindow(); | |
| void UpdateWidth(float newWidth); | |
| void AddCard(const char* text); | |
| void InsertCard(const char* text); // New method | |
| virtual void GetPreferredSize(float* width, float* height); | |
| private: | |
| void RelayoutCards(); | |
| float fContentHeight; | |
| float fCurrentWidth; | |
| float fInitialWidth; | |
| }; | |
| ContentView::ContentView(float initialWidth) | |
| : | |
| BView("content", B_WILL_DRAW), | |
| fContentHeight(0.0f), | |
| fCurrentWidth(-1.0f), | |
| fInitialWidth(initialWidth) | |
| { | |
| SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); | |
| const char* texts[] = { | |
| "Short text.", | |
| "This is a medium length text that should wrap to multiple lines " | |
| "depending on the width of the card.", | |
| "Brief.", | |
| "Haiku is an open-source operating system that specifically targets " | |
| "personal computing. It is inspired by the Be Operating System (BeOS), " | |
| "which was known for its clean design and responsive interface. The goal " | |
| "of Haiku is to create a fast, efficient, and easy-to-use desktop " | |
| "operating system that preserves the spirit of BeOS.", | |
| "Another short one.", | |
| "The BView class is one of the fundamental building blocks of Haiku's " | |
| "interface toolkit. It represents a rectangular area on screen and can " | |
| "handle drawing, mouse events, and keyboard events. Views can be nested " | |
| "to create complex user interfaces.", | |
| "Test.", | |
| "Scroll views are essential for displaying content that exceeds the " | |
| "available screen space. They provide scrollbars that allow users to " | |
| "navigate through the content. In Haiku, the BScrollView class wraps " | |
| "another view to provide this functionality.", | |
| "Cards are a popular UI pattern that groups related information together " | |
| "in a visually distinct container. They typically have a background " | |
| "color, some padding to separate content from edges, and sometimes " | |
| "rounded corners or shadows for depth.", | |
| "Hi!", | |
| "The layout system in Haiku uses a constraint-based approach that makes " | |
| "it easier to create responsive interfaces. The BLayout classes handle " | |
| "the positioning and sizing of child views automatically, reducing the " | |
| "need for manual frame calculations.", | |
| "OK.", | |
| "Text wrapping is an important feature for displaying long passages of " | |
| "text in a constrained space. Without it, text would either overflow " | |
| "the visible area or be truncated, making it difficult to read. " | |
| "BTextView provides built-in support for word wrapping.", | |
| "Simple.", | |
| "When implementing card-based interfaces, it's important to consider " | |
| "the performance implications. Each card is a separate view with its " | |
| "own drawing context and event handling. For small numbers of cards, " | |
| "this is fine, but for hundreds or thousands of cards, more efficient " | |
| "approaches like custom drawing or view recycling may be necessary.", | |
| "Yep.", | |
| "The color scheme of an application can significantly affect its " | |
| "usability and aesthetics. Haiku provides several predefined color " | |
| "constants like B_DOCUMENT_BACKGROUND_COLOR and B_PANEL_BACKGROUND_COLOR " | |
| "that help maintain consistency with the system theme.", | |
| "Hey there!", | |
| "In this demo, we have twenty cards with varying amounts of text to " | |
| "demonstrate how the scroll view and card layout work together. The " | |
| "scroll view automatically adjusts its scrollbars based on the total " | |
| "height of the content.", | |
| "End." | |
| }; | |
| const int kNumCards = 20; | |
| for (int i = 0; i < kNumCards; i++) { | |
| BString name; | |
| name << "Card" << (i + 1); | |
| CardView* card = new CardView(name.String(), texts[i], fInitialWidth); | |
| AddChild(card); | |
| } | |
| } | |
| void | |
| ContentView::AttachedToWindow() | |
| { | |
| UpdateWidth(fInitialWidth); | |
| } | |
| void | |
| ContentView::UpdateWidth(float newWidth) | |
| { | |
| if (newWidth == fCurrentWidth) | |
| return; | |
| fCurrentWidth = newWidth; | |
| RelayoutCards(); | |
| } | |
| void | |
| ContentView::RelayoutCards() | |
| { | |
| const float kCardSpacing = 10.0f; | |
| const float kLeftMargin = 20.0f; | |
| float cardWidth = fCurrentWidth - 2.0f * kLeftMargin; | |
| if (cardWidth < 50.0f) cardWidth = 50.0f; | |
| float yOffset = 20.0f; | |
| // Loops through children in list order (0 to N) | |
| for (int i = 0; i < CountChildren(); i++) { | |
| CardView* card = static_cast<CardView*>(ChildAt(i)); | |
| if (card != NULL) { | |
| card->SetWidth(cardWidth); | |
| card->MoveTo(kLeftMargin, yOffset); | |
| yOffset += card->Bounds().Height() + kCardSpacing; | |
| } | |
| } | |
| fContentHeight = yOffset + 20.0f; | |
| ResizeTo(fCurrentWidth, fContentHeight); | |
| } | |
| void | |
| ContentView::AddCard(const char* text) | |
| { | |
| const float kCardSpacing = 10.0f; | |
| const float kLeftMargin = 20.0f; | |
| int cardNum = CountChildren() + 1; | |
| BString name; | |
| name << "Card" << cardNum; | |
| float cardWidth = fCurrentWidth - 2.0f * kLeftMargin; | |
| if (cardWidth < 50.0f) cardWidth = 50.0f; | |
| CardView* card = new CardView(name.String(), text, cardWidth); | |
| AddChild(card); | |
| card->SetWidth(cardWidth); | |
| float yOffset = fContentHeight - 20.0f; | |
| card->MoveTo(kLeftMargin, yOffset); | |
| fContentHeight = yOffset + card->Bounds().Height() + kCardSpacing + 20.0f; | |
| ResizeTo(fCurrentWidth, fContentHeight); | |
| } | |
| void | |
| ContentView::InsertCard(const char* text) | |
| { | |
| const float kLeftMargin = 20.0f; | |
| int cardNum = CountChildren() + 1; | |
| BString name; | |
| name << "Card" << cardNum; | |
| float cardWidth = fCurrentWidth - 2.0f * kLeftMargin; | |
| if (cardWidth < 50.0f) cardWidth = 50.0f; | |
| CardView* card = new CardView(name.String(), text, cardWidth); | |
| // Insert at the very beginning of the child list (index 0) | |
| BView* firstChild = ChildAt(0); | |
| if (firstChild != NULL) | |
| AddChild(card, firstChild); | |
| else | |
| AddChild(card); | |
| // Recalculate layout. Since the new card is at index 0, it gets placed | |
| // at the top, and all other cards are pushed down automatically! | |
| RelayoutCards(); | |
| } | |
| void | |
| ContentView::GetPreferredSize(float* width, float* height) | |
| { | |
| if (width != NULL) | |
| *width = fCurrentWidth > 0 ? fCurrentWidth : fInitialWidth; | |
| if (height != NULL) | |
| *height = fContentHeight; | |
| } | |
| // ============================================================================ | |
| // MainWindow | |
| // ============================================================================ | |
| class MainWindow : public BWindow { | |
| public: | |
| MainWindow(); | |
| virtual bool QuitRequested(); | |
| virtual void FrameResized(float newWidth, float newHeight); | |
| virtual void MessageReceived(BMessage* message); | |
| private: | |
| ContentView* fContentView; | |
| BScrollView* fScrollView; | |
| BMenuBar* fMenuBar; | |
| static const float kMenuHeight; | |
| }; | |
| const float MainWindow::kMenuHeight = 25.0f; | |
| MainWindow::MainWindow() | |
| : | |
| BWindow(BRect(50, 50, 450, 400), "Card Demo", | |
| B_DOCUMENT_WINDOW, 0) | |
| { | |
| // 1. Menu Bar | |
| fMenuBar = new BMenuBar("menubar"); | |
| BMenu* fileMenu = new BMenu("File"); | |
| fileMenu->AddItem(new BMenuItem("Add Card", new BMessage(kMsgAddCard), 'N')); | |
| fileMenu->AddItem(new BMenuItem("Insert Card", new BMessage(kMsgInsertCard), 'I')); | |
| fileMenu->AddSeparatorItem(); | |
| fileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q')); | |
| fMenuBar->AddItem(fileMenu); | |
| AddChild(fMenuBar); | |
| // 2. Fixed dimensions for manual layout | |
| const float kBorder = 2.0f; | |
| const float kScrollBarWidth = 16.0f; | |
| float initialContentWidth = Bounds().Width() - (2.0f * kBorder) - kScrollBarWidth; | |
| fContentView = new ContentView(initialContentWidth); | |
| // 3. Scroll View | |
| fScrollView = new BScrollView("scroll", fContentView, | |
| 0, false, true, B_FANCY_BORDER); | |
| AddChild(fScrollView); | |
| // 4. Manual Positioning | |
| fMenuBar->MoveTo(0, 0); | |
| fMenuBar->ResizeTo(Bounds().Width(), kMenuHeight); | |
| fScrollView->MoveTo(0, kMenuHeight); | |
| fScrollView->ResizeTo(Bounds().Width(), Bounds().Height() - kMenuHeight); | |
| SetSizeLimits(300, 2000, 200, 2000); | |
| Show(); | |
| } | |
| bool | |
| MainWindow::QuitRequested() | |
| { | |
| be_app->PostMessage(B_QUIT_REQUESTED); | |
| return true; | |
| } | |
| void | |
| MainWindow::FrameResized(float newWidth, float newHeight) | |
| { | |
| BWindow::FrameResized(newWidth, newHeight); | |
| const float kBorder = 2.0f; | |
| fMenuBar->ResizeTo(newWidth, kMenuHeight); | |
| fScrollView->MoveTo(0, kMenuHeight); | |
| fScrollView->ResizeTo(newWidth, newHeight - kMenuHeight); | |
| float vScrollBarWidth = 0.0f; | |
| BScrollBar* vScroll = fScrollView->ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL && !vScroll->IsHidden()) | |
| vScrollBarWidth = vScroll->Bounds().Width(); | |
| float contentWidth = newWidth - (2.0f * kBorder) - vScrollBarWidth; | |
| fContentView->UpdateWidth(contentWidth); | |
| } | |
| void | |
| MainWindow::MessageReceived(BMessage* message) | |
| { | |
| switch (message->what) { | |
| case kMsgAddCard: | |
| { | |
| const char* dummyText = "This is a dynamically added card. " | |
| "It demonstrates that we can append views to the content area " | |
| "at runtime, and the scroll view will correctly adjust to show " | |
| "the new content at the bottom."; | |
| fContentView->AddCard(dummyText); | |
| BScrollBar* vScroll = fScrollView->ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL) { | |
| float visibleHeight = fScrollView->Bounds().Height() - 4.0f; | |
| float totalHeight = fContentView->Bounds().Height(); | |
| if (totalHeight > visibleHeight) { | |
| vScroll->SetRange(0, totalHeight - visibleHeight); | |
| vScroll->SetProportion(visibleHeight / totalHeight); | |
| } else { | |
| vScroll->SetRange(0, 0); | |
| vScroll->SetProportion(1.0f); | |
| } | |
| } | |
| fScrollView->Invalidate(); | |
| break; | |
| } | |
| case kMsgInsertCard: | |
| { | |
| const char* dummyText = "This is an inserted card at the top. " | |
| "It pushes all existing cards downward, demonstrating how " | |
| "to insert views dynamically into the beginning of a list."; | |
| fContentView->InsertCard(dummyText); | |
| // Update scroll bar proportions because total height changed | |
| BScrollBar* vScroll = fScrollView->ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL) { | |
| float visibleHeight = fScrollView->Bounds().Height() - 4.0f; | |
| float totalHeight = fContentView->Bounds().Height(); | |
| if (totalHeight > visibleHeight) { | |
| vScroll->SetRange(0, totalHeight - visibleHeight); | |
| vScroll->SetProportion(visibleHeight / totalHeight); | |
| } else { | |
| vScroll->SetRange(0, 0); | |
| vScroll->SetProportion(1.0f); | |
| } | |
| // Scroll all the way to the top so the user can see the new card | |
| vScroll->SetValue(0); | |
| } | |
| fScrollView->Invalidate(); | |
| break; | |
| } | |
| default: | |
| BWindow::MessageReceived(message); | |
| break; | |
| } | |
| } | |
| // ============================================================================ | |
| // CardApp | |
| // ============================================================================ | |
| class CardApp : public BApplication { | |
| public: | |
| CardApp(); | |
| virtual void ReadyToRun(); | |
| }; | |
| CardApp::CardApp() | |
| : | |
| BApplication(kAppSignature) | |
| { | |
| } | |
| void | |
| CardApp::ReadyToRun() | |
| { | |
| new MainWindow(); | |
| } | |
| // ============================================================================ | |
| // main | |
| // ============================================================================ | |
| int | |
| main() | |
| { | |
| CardApp app; | |
| app.Run(); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This version use layout builder for most of layout making less calculation | |
| #include <Application.h> | |
| #include <Window.h> | |
| #include <View.h> | |
| #include <ScrollView.h> | |
| #include <TextView.h> | |
| #include <String.h> | |
| #include <MenuBar.h> | |
| #include <Menu.h> | |
| #include <MenuItem.h> | |
| #include <LayoutBuilder.h> | |
| #include <Layout.h> | |
| #include <iostream> | |
| static const char* kAppSignature = "application/x-vnd.Test-CardDemo"; | |
| static const uint32 kMsgAddCard = 'adcd'; | |
| static const uint32 kMsgInsertCard = 'incd'; // New message for Insert | |
| // ============================================================================ | |
| // CardView - A card containing wrapped text | |
| // ============================================================================ | |
| class CardView : public BView { | |
| public: | |
| CardView(const char* name, const char* text); | |
| virtual void FrameResized(float newWidth, float newHeight); | |
| virtual bool HasHeightForWidth(); | |
| virtual void GetHeightForWidth(float width, float* min, float* max, float* preferred); | |
| private: | |
| BTextView* fTextView; | |
| static const float kInset; | |
| }; | |
| const float CardView::kInset = 15.0f; | |
| CardView::CardView(const char* name, const char* text) | |
| : | |
| BView(name, B_WILL_DRAW | B_FRAME_EVENTS), | |
| fTextView(NULL) | |
| { | |
| SetViewColor(255, 255, 255); | |
| fTextView = new BTextView("text", B_WILL_DRAW); | |
| fTextView->SetText(text); | |
| fTextView->SetViewColor(255, 255, 255); | |
| fTextView->MakeEditable(false); | |
| fTextView->SetWordWrap(true); | |
| fTextView->SetStylable(false); | |
| fTextView->SetFlags(fTextView->Flags() & ~(B_NAVIGABLE | B_FRAME_EVENTS)); | |
| rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR); | |
| fTextView->SetFontAndColor(NULL, 0, &textColor); | |
| AddChild(fTextView); | |
| } | |
| bool | |
| CardView::HasHeightForWidth() | |
| { | |
| return true; | |
| } | |
| void | |
| CardView::GetHeightForWidth(float width, float* min, float* max, float* preferred) | |
| { | |
| float textWidth = width - 2.0f * kInset; | |
| if (textWidth < 10.0f) textWidth = 10.0f; | |
| BRect oldRect = fTextView->TextRect(); | |
| BRect tempRect(0, 0, textWidth, 0); | |
| fTextView->SetTextRect(tempRect); | |
| float textHeight = fTextView->TextHeight(0, fTextView->TextLength()); | |
| fTextView->SetTextRect(oldRect); | |
| float totalHeight = textHeight + 2.0f * kInset; | |
| if (min) *min = totalHeight; | |
| if (max) *max = totalHeight; | |
| if (preferred) *preferred = totalHeight; | |
| } | |
| void | |
| CardView::FrameResized(float newWidth, float newHeight) | |
| { | |
| BView::FrameResized(newWidth, newHeight); | |
| float textWidth = newWidth - 2.0f * kInset; | |
| if (textWidth < 10.0f) textWidth = 10.0f; | |
| float textHeight = newHeight - 2.0f * kInset; | |
| if (textHeight < 0.0f) textHeight = 0.0f; | |
| fTextView->MoveTo(kInset, kInset); | |
| fTextView->ResizeTo(textWidth, textHeight); | |
| fTextView->SetTextRect(BRect(0, 0, textWidth, textHeight)); | |
| } | |
| // ============================================================================ | |
| // ContentView - Holds all cards, placed inside a BScrollView | |
| // ============================================================================ | |
| class ContentView : public BView { | |
| public: | |
| ContentView(); | |
| void UpdateWidth(float newWidth); | |
| void AddCard(const char* text); | |
| void InsertCard(const char* text); // New method | |
| float TotalHeight() const; | |
| private: | |
| float fCurrentWidth; | |
| BGroupLayout* fLayout; | |
| }; | |
| ContentView::ContentView() | |
| : | |
| BView("content", B_WILL_DRAW | B_FRAME_EVENTS), | |
| fCurrentWidth(-1.0f), | |
| fLayout(NULL) | |
| { | |
| SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); | |
| fLayout = new BGroupLayout(B_VERTICAL); | |
| SetLayout(fLayout); | |
| fLayout->SetSpacing(10.0f); | |
| fLayout->SetInsets(20.0f, 20.0f, 20.0f, 20.0f); | |
| } | |
| void | |
| ContentView::UpdateWidth(float newWidth) | |
| { | |
| #if 0 | |
| if (newWidth == fCurrentWidth) { | |
| std::cout << "ContentView::UpdateWidth " << "Same width" << std::endl; | |
| return; | |
| } | |
| #endif | |
| fCurrentWidth = newWidth; | |
| } | |
| float | |
| ContentView::TotalHeight() const | |
| { | |
| float cardWidth = fCurrentWidth - 40.0f; | |
| if (cardWidth < 20.0f) cardWidth = 20.0f; | |
| float totalHeight = 0.0f; | |
| int numChildren = CountChildren(); | |
| for (int i = 0; i < numChildren; i++) { | |
| CardView* card = static_cast<CardView*>(ChildAt(i)); | |
| if (card != NULL) { | |
| float min, max, preferred; | |
| card->GetHeightForWidth(cardWidth, &min, &max, &preferred); | |
| totalHeight += preferred; | |
| } | |
| } | |
| if (numChildren > 1) { | |
| totalHeight += (numChildren - 1) * 10.0f; | |
| } | |
| totalHeight += 40.0f; | |
| return totalHeight; | |
| } | |
| void | |
| ContentView::AddCard(const char* text) | |
| { | |
| int cardNum = CountChildren() + 1; | |
| BString name; | |
| name << "Card" << cardNum; | |
| CardView* card = new CardView(name.String(), text); | |
| AddChild(card); | |
| } | |
| void | |
| ContentView::InsertCard(const char* text) | |
| { | |
| int cardNum = CountChildren() + 1; | |
| BString name; | |
| name << "Card" << cardNum; | |
| CardView* card = new CardView(name.String(), text); | |
| // Insert at the very beginning of the child list (index 0) | |
| BView* firstChild = ChildAt(0); | |
| if (firstChild != NULL) | |
| AddChild(card, firstChild); | |
| else | |
| AddChild(card); | |
| } | |
| // ============================================================================ | |
| // CardScrollView - Intercepts resizing to update width | |
| // ============================================================================ | |
| class CardScrollView : public BScrollView { | |
| public: | |
| CardScrollView(const char* name, ContentView* target); | |
| virtual void FrameResized(float newWidth, float newHeight); | |
| void UpdateScrollBar(); | |
| private: | |
| ContentView* fContentView; | |
| }; | |
| CardScrollView::CardScrollView(const char* name, ContentView* target) | |
| : | |
| BScrollView(name, target, 0, false, true, B_FANCY_BORDER), | |
| fContentView(target) | |
| { | |
| } | |
| void | |
| CardScrollView::FrameResized(float newWidth, float newHeight) | |
| { | |
| std::cout << "CardScrollView " << newWidth << " & " << newHeight << std::endl; | |
| std::cout << "CardScrollView " << Bounds().Width() << " & " << Bounds().Height() << std::endl; | |
| BScrollView::FrameResized(newWidth, newHeight); | |
| float vScrollBarWidth = 0.0f; | |
| BScrollBar* vScroll = ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL && !vScroll->IsHidden()) | |
| vScrollBarWidth = vScroll->Bounds().Width(); | |
| std::cout << "ScrollBar " << vScroll->Bounds().Width() << " & " << vScroll->Bounds().Height() << std::endl; | |
| const float kBorder = 2.0f; | |
| float contentWidth = newWidth - (2.0f * kBorder) - vScrollBarWidth; | |
| dynamic_cast<ContentView*>(Target())->UpdateWidth(contentWidth); | |
| UpdateScrollBar(); | |
| } | |
| void | |
| CardScrollView::UpdateScrollBar() | |
| { | |
| BScrollBar* vScroll = ScrollBar(B_VERTICAL); | |
| if (vScroll == NULL) | |
| return; | |
| float totalHeight = fContentView->TotalHeight(); | |
| float viewportHeight = Bounds().Height() - (2.0f * 2.0f); | |
| if (totalHeight <= viewportHeight) { | |
| vScroll->SetRange(0.0f, 0.0f); | |
| } else { | |
| vScroll->SetRange(0.0f, totalHeight - viewportHeight); | |
| vScroll->SetProportion(viewportHeight / totalHeight); | |
| } | |
| } | |
| // ============================================================================ | |
| // MainWindow | |
| // ============================================================================ | |
| class MainWindow : public BWindow { | |
| public: | |
| MainWindow(); | |
| virtual bool QuitRequested(); | |
| virtual void MessageReceived(BMessage* message); | |
| private: | |
| ContentView* fContentView; | |
| CardScrollView* fScrollView; | |
| BMenuBar* fMenuBar; | |
| static const float kMenuHeight; | |
| }; | |
| const float MainWindow::kMenuHeight = 25.0f; | |
| MainWindow::MainWindow() | |
| : | |
| BWindow(BRect(50, 50, 450, 400), "Card Demo", | |
| B_DOCUMENT_WINDOW, 0) | |
| { | |
| // 1. Menu Bar | |
| fMenuBar = new BMenuBar("menubar"); | |
| BMenu* fileMenu = new BMenu("File"); | |
| fileMenu->AddItem(new BMenuItem("Add Card", new BMessage(kMsgAddCard), 'N')); | |
| fileMenu->AddItem(new BMenuItem("Insert Card", new BMessage(kMsgInsertCard), 'I')); | |
| fileMenu->AddSeparatorItem(); | |
| fileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q')); | |
| fMenuBar->AddItem(fileMenu); | |
| // 2. Fixed dimensions for manual layout | |
| // const float kBorder = 2.0f; | |
| // const float kScrollBarWidth = 16.0f; | |
| // float initialContentWidth = Bounds().Width() - (2.0f * kBorder) - kScrollBarWidth; | |
| fContentView = new ContentView(); | |
| // 3. Scroll View | |
| fScrollView = new CardScrollView("scroll", fContentView); | |
| // Create dummy text view | |
| BTextView *textView = new BTextView("textView", B_WILL_DRAW); | |
| textView->SetText("This is a summary view showing the selected article content. " | |
| "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " | |
| "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); | |
| // Create horizontal split view for left/right panels | |
| BSplitView* mainSplit = new BSplitView(B_HORIZONTAL, 0.0f); | |
| mainSplit->AddChild(textView, 0.3f); | |
| mainSplit->AddChild(fScrollView, 0.7f); | |
| textView->SetExplicitMinSize(BSize(50, B_SIZE_UNSET)); | |
| // Build layout | |
| BLayoutBuilder::Group<>(this, B_VERTICAL) | |
| .Add(fMenuBar) | |
| .Add(mainSplit) | |
| .End(); | |
| const char* texts[] = { | |
| "Short text.", | |
| "This is a medium length text that should wrap to multiple lines " | |
| "depending on the width of the card.", | |
| "Brief.", | |
| "Haiku is an open-source operating system that specifically targets " | |
| "personal computing. It is inspired by the Be Operating System (BeOS), " | |
| "which was known for its clean design and responsive interface. The goal " | |
| "of Haiku is to create a fast, efficient, and easy-to-use desktop " | |
| "operating system that preserves the spirit of BeOS.", | |
| "Another short one.", | |
| "The BView class is one of the fundamental building blocks of Haiku's " | |
| "interface toolkit. It represents a rectangular area on screen and can " | |
| "handle drawing, mouse events, and keyboard events. Views can be nested " | |
| "to create complex user interfaces.", | |
| "Test.", | |
| "Scroll views are essential for displaying content that exceeds the " | |
| "available screen space. They provide scrollbars that allow users to " | |
| "navigate through the content. In Haiku, the BScrollView class wraps " | |
| "another view to provide this functionality.", | |
| "Cards are a popular UI pattern that groups related information together " | |
| "in a visually distinct container. They typically have a background " | |
| "color, some padding to separate content from edges, and sometimes " | |
| "rounded corners or shadows for depth.", | |
| "Hi!", | |
| "The layout system in Haiku uses a constraint-based approach that makes " | |
| "it easier to create responsive interfaces. The BLayout classes handle " | |
| "the positioning and sizing of child views automatically, reducing the " | |
| "need for manual frame calculations.", | |
| "OK.", | |
| "Text wrapping is an important feature for displaying long passages of " | |
| "text in a constrained space. Without it, text would either overflow " | |
| "the visible area or be truncated, making it difficult to read. " | |
| "BTextView provides built-in support for word wrapping.", | |
| "Simple.", | |
| "When implementing card-based interfaces, it's important to consider " | |
| "the performance implications. Each card is a separate view with its " | |
| "own drawing context and event handling. For small numbers of cards, " | |
| "this is fine, but for hundreds or thousands of cards, more efficient " | |
| "approaches like custom drawing or view recycling may be necessary.", | |
| "Yep.", | |
| "The color scheme of an application can significantly affect its " | |
| "usability and aesthetics. Haiku provides several predefined color " | |
| "constants like B_DOCUMENT_BACKGROUND_COLOR and B_PANEL_BACKGROUND_COLOR " | |
| "that help maintain consistency with the system theme.", | |
| "Hey there!", | |
| "In this demo, we have twenty cards with varying amounts of text to " | |
| "demonstrate how the scroll view and card layout work together. The " | |
| "scroll view automatically adjusts its scrollbars based on the total " | |
| "height of the content.", | |
| "End." | |
| }; | |
| const int kNumCards = 20; | |
| for (int i = 0; i < kNumCards; i++) { | |
| BString name; | |
| name << "Card" << (i + 1); | |
| CardView* card = new CardView(name.String(), texts[i]); | |
| fContentView->AddChild(card); | |
| } | |
| fScrollView->UpdateScrollBar(); | |
| Show(); | |
| } | |
| bool | |
| MainWindow::QuitRequested() | |
| { | |
| be_app->PostMessage(B_QUIT_REQUESTED); | |
| return true; | |
| } | |
| void | |
| MainWindow::MessageReceived(BMessage* message) | |
| { | |
| switch (message->what) { | |
| case kMsgAddCard: | |
| { | |
| const char* dummyText = "This is a dynamically added card. " | |
| "It demonstrates that we can append views to the content area " | |
| "at runtime, and the scroll view will correctly adjust to show " | |
| "the new content at the bottom."; | |
| fContentView->AddCard(dummyText); | |
| fScrollView->Invalidate(); | |
| fScrollView->UpdateScrollBar(); | |
| break; | |
| } | |
| case kMsgInsertCard: | |
| { | |
| const char* dummyText = "This is an inserted card at the top. " | |
| "It pushes all existing cards downward, demonstrating how " | |
| "to insert views dynamically into the beginning of a list."; | |
| fContentView->InsertCard(dummyText); | |
| BScrollBar* vScroll = fScrollView->ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL) { | |
| fScrollView->UpdateScrollBar(); | |
| vScroll->SetValue(0); | |
| } | |
| fScrollView->Invalidate(); | |
| break; | |
| } | |
| default: | |
| BWindow::MessageReceived(message); | |
| break; | |
| } | |
| } | |
| // ============================================================================ | |
| // CardApp | |
| // ============================================================================ | |
| class CardApp : public BApplication { | |
| public: | |
| CardApp(); | |
| virtual void ReadyToRun(); | |
| }; | |
| CardApp::CardApp() | |
| : | |
| BApplication(kAppSignature) | |
| { | |
| } | |
| void | |
| CardApp::ReadyToRun() | |
| { | |
| new MainWindow(); | |
| } | |
| // ============================================================================ | |
| // main | |
| // ============================================================================ | |
| int | |
| main() | |
| { | |
| CardApp app; | |
| app.Run(); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This is a version with splitview | |
| #include <Application.h> | |
| #include <Window.h> | |
| #include <View.h> | |
| #include <ScrollView.h> | |
| #include <TextView.h> | |
| #include <String.h> | |
| #include <MenuBar.h> | |
| #include <Menu.h> | |
| #include <MenuItem.h> | |
| #include <LayoutBuilder.h> | |
| #include <iostream> | |
| static const char* kAppSignature = "application/x-vnd.Test-CardDemo"; | |
| static const uint32 kMsgAddCard = 'adcd'; | |
| static const uint32 kMsgInsertCard = 'incd'; // New message for Insert | |
| // ============================================================================ | |
| // CardView - A card containing wrapped text | |
| // ============================================================================ | |
| class CardView : public BView { | |
| public: | |
| CardView(const char* name, const char* text); | |
| void SetWidth(float width); | |
| virtual void Draw(BRect updateRect); | |
| private: | |
| BTextView* fTextView; | |
| static const float kInset; | |
| }; | |
| const float CardView::kInset = 15.0f; | |
| CardView::CardView(const char* name, const char* text) | |
| : | |
| BView(name, B_WILL_DRAW | B_FRAME_EVENTS), | |
| fTextView(NULL) | |
| { | |
| SetViewColor(255, 255, 255); | |
| fTextView = new BTextView("text", B_WILL_DRAW); | |
| fTextView->SetText(text); | |
| fTextView->SetViewColor(255, 255, 255); | |
| fTextView->MakeEditable(false); | |
| fTextView->SetWordWrap(true); | |
| fTextView->SetStylable(false); | |
| fTextView->SetFlags(fTextView->Flags() & ~(B_NAVIGABLE | B_FRAME_EVENTS)); | |
| rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR); | |
| fTextView->SetFontAndColor(NULL, 0, &textColor); | |
| AddChild(fTextView); | |
| } | |
| void | |
| CardView::SetWidth(float width) | |
| { | |
| float fWidth = width; | |
| float textWidth = fWidth - 2.0f * kInset; | |
| if (textWidth < 10.0f) textWidth = 10.0f; | |
| // Use TextRect to change wrap boundaries without forcing an active structural view resize loop | |
| BRect textRect(0, 0, textWidth, 0); | |
| fTextView->SetTextRect(textRect); | |
| // Request total wrapped height | |
| float textHeight = fTextView->TextHeight(0, fTextView->TextLength()); | |
| float totalHeight = textHeight + 2.0f * kInset; | |
| // Perform a singular, unified layout pass | |
| MoveTo(Frame().left, Frame().top); | |
| ResizeTo(fWidth, totalHeight); | |
| fTextView->MoveTo(kInset, kInset); | |
| fTextView->ResizeTo(textWidth, textHeight); | |
| fTextView->SetTextRect(BRect(0, 0, textWidth, textHeight)); | |
| } | |
| void | |
| CardView::Draw(BRect updateRect) | |
| { | |
| BView::Draw(updateRect); | |
| SetHighColor(200, 200, 200); | |
| StrokeRect(Bounds()); | |
| } | |
| // ============================================================================ | |
| // ContentView - Holds all cards, placed inside a BScrollView | |
| // ============================================================================ | |
| class ContentView : public BView { | |
| public: | |
| ContentView(); | |
| virtual void AttachedToWindow(); | |
| void UpdateWidth(float newWidth); | |
| void AddCard(const char* text); | |
| void InsertCard(const char* text); // New method | |
| virtual void FrameResized(float newWidth, float newHeight); | |
| private: | |
| void RelayoutCards(); | |
| float fContentHeight; | |
| float fCurrentWidth; | |
| float fInitialWidth; | |
| }; | |
| ContentView::ContentView() | |
| : | |
| BView("content", B_WILL_DRAW | B_FRAME_EVENTS), | |
| fContentHeight(0.0f), | |
| fCurrentWidth(-1.0f) | |
| { | |
| SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); | |
| fInitialWidth = Bounds().Width(); | |
| } | |
| void | |
| ContentView::AttachedToWindow() | |
| { | |
| UpdateWidth(Bounds().Width()); | |
| } | |
| void | |
| ContentView::UpdateWidth(float newWidth) | |
| { | |
| #if 0 | |
| if (newWidth == fCurrentWidth) { | |
| std::cout << "ContentView::UpdateWidth " << "Same width" << std::endl; | |
| return; | |
| } | |
| #endif | |
| fCurrentWidth = newWidth; | |
| RelayoutCards(); | |
| } | |
| void | |
| ContentView::RelayoutCards() | |
| { | |
| const float kCardSpacing = 10.0f; | |
| const float kLeftMargin = 20.0f; | |
| float cardWidth = fCurrentWidth - 2.0f * kLeftMargin; | |
| if (cardWidth < 20.0f) cardWidth = 20.0f; | |
| float yOffset = 20.0f; | |
| // Loops through children in list order (0 to N) | |
| for (int i = 0; i < CountChildren(); i++) { | |
| CardView* card = static_cast<CardView*>(ChildAt(i)); | |
| if (card != NULL) { | |
| card->SetWidth(cardWidth); | |
| card->MoveTo(kLeftMargin, yOffset); | |
| yOffset += card->Bounds().Height() + kCardSpacing; | |
| } | |
| } | |
| fContentHeight = yOffset + 20.0f; | |
| // 2. Query our visible frame height (the viewport size) | |
| float visibleHeight = Bounds().Height(); | |
| // 3. Find the Scroll View's Vertical Scrollbar | |
| BScrollBar* vBar = ScrollBar(B_VERTICAL); | |
| if (vBar != NULL) { | |
| if (fContentHeight <= visibleHeight) { | |
| // Entire list fits on screen; disable scrollbar tracking | |
| vBar->SetRange(0.0f, 0.0f); | |
| } else { | |
| // Content is taller than the viewport window. | |
| // Maximum scroll point is total virtual height minus what's currently visible. | |
| float maxScrollLimit = fContentHeight - visibleHeight; | |
| vBar->SetRange(0.0f, maxScrollLimit); | |
| // Adjust the size of the scrollbar knob proportionally | |
| vBar->SetProportion(visibleHeight / fContentHeight); | |
| } | |
| } | |
| // 4. Force a repaint of the updated layout matrix | |
| Invalidate(); | |
| std::cout << "Relayout Finished" << std::endl << std::endl; | |
| } | |
| void | |
| ContentView::AddCard(const char* text) | |
| { | |
| int cardNum = CountChildren() + 1; | |
| BString name; | |
| name << "Card" << cardNum; | |
| CardView* card = new CardView(name.String(), text); | |
| AddChild(card); | |
| RelayoutCards(); | |
| } | |
| void | |
| ContentView::InsertCard(const char* text) | |
| { | |
| int cardNum = CountChildren() + 1; | |
| BString name; | |
| name << "Card" << cardNum; | |
| CardView* card = new CardView(name.String(), text); | |
| // Insert at the very beginning of the child list (index 0) | |
| BView* firstChild = ChildAt(0); | |
| if (firstChild != NULL) | |
| AddChild(card, firstChild); | |
| else | |
| AddChild(card); | |
| // Recalculate layout. Since the new card is at index 0, it gets placed | |
| // at the top, and all other cards are pushed down automatically! | |
| RelayoutCards(); | |
| } | |
| void | |
| ContentView::FrameResized(float newWidth, float newHeight) | |
| { | |
| BView::FrameResized(newWidth, newHeight); | |
| RelayoutCards(); | |
| } | |
| // ============================================================================ | |
| // CardScrollView - Intercepts resizing to update width | |
| // ============================================================================ | |
| class CardScrollView : public BScrollView { | |
| public: | |
| CardScrollView(const char* name, ContentView* target); | |
| virtual void FrameResized(float newWidth, float newHeight); | |
| private: | |
| ContentView* fContentView; | |
| }; | |
| CardScrollView::CardScrollView(const char* name, ContentView* target) | |
| : | |
| BScrollView(name, target, 0, false, true, B_FANCY_BORDER), | |
| fContentView(target) | |
| { | |
| } | |
| void | |
| CardScrollView::FrameResized(float newWidth, float newHeight) | |
| { | |
| std::cout << "CardScrollView " << newWidth << " & " << newHeight << std::endl; | |
| std::cout << "CardScrollView " << Bounds().Width() << " & " << Bounds().Height() << std::endl; | |
| BScrollView::FrameResized(newWidth, newHeight); | |
| float vScrollBarWidth = 0.0f; | |
| BScrollBar* vScroll = ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL && !vScroll->IsHidden()) | |
| vScrollBarWidth = vScroll->Bounds().Width(); | |
| std::cout << "ScrollBar " << vScroll->Bounds().Width() << " & " << vScroll->Bounds().Height() << std::endl; | |
| const float kBorder = 2.0f; | |
| float contentWidth = newWidth - (2.0f * kBorder) - vScrollBarWidth; | |
| dynamic_cast<ContentView*>(Target())->UpdateWidth(contentWidth); | |
| } | |
| // ============================================================================ | |
| // MainWindow | |
| // ============================================================================ | |
| class MainWindow : public BWindow { | |
| public: | |
| MainWindow(); | |
| virtual bool QuitRequested(); | |
| virtual void MessageReceived(BMessage* message); | |
| private: | |
| ContentView* fContentView; | |
| BScrollView* fScrollView; | |
| BMenuBar* fMenuBar; | |
| static const float kMenuHeight; | |
| }; | |
| const float MainWindow::kMenuHeight = 25.0f; | |
| MainWindow::MainWindow() | |
| : | |
| BWindow(BRect(50, 50, 450, 400), "Card Demo", | |
| B_DOCUMENT_WINDOW, 0) | |
| { | |
| // 1. Menu Bar | |
| fMenuBar = new BMenuBar("menubar"); | |
| BMenu* fileMenu = new BMenu("File"); | |
| fileMenu->AddItem(new BMenuItem("Add Card", new BMessage(kMsgAddCard), 'N')); | |
| fileMenu->AddItem(new BMenuItem("Insert Card", new BMessage(kMsgInsertCard), 'I')); | |
| fileMenu->AddSeparatorItem(); | |
| fileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q')); | |
| fMenuBar->AddItem(fileMenu); | |
| // 2. Fixed dimensions for manual layout | |
| const float kBorder = 2.0f; | |
| const float kScrollBarWidth = 16.0f; | |
| float initialContentWidth = Bounds().Width() - (2.0f * kBorder) - kScrollBarWidth; | |
| fContentView = new ContentView(); | |
| // 3. Scroll View | |
| fScrollView = new CardScrollView("scroll", fContentView); | |
| // Create dummy text view | |
| BTextView *textView = new BTextView("textView", B_WILL_DRAW); | |
| textView->SetText("This is a summary view showing the selected article content. " | |
| "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " | |
| "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); | |
| // Create horizontal split view for left/right panels | |
| BSplitView* mainSplit = new BSplitView(B_HORIZONTAL, 0.0f); | |
| mainSplit->AddChild(textView, 0.3f); | |
| mainSplit->AddChild(fScrollView, 0.7f); | |
| textView->SetExplicitMinSize(BSize(50, B_SIZE_UNSET)); | |
| // Build layout | |
| BLayoutBuilder::Group<>(this, B_VERTICAL) | |
| .Add(fMenuBar) | |
| .Add(mainSplit) | |
| .End(); | |
| const char* texts[] = { | |
| "Short text.", | |
| "This is a medium length text that should wrap to multiple lines " | |
| "depending on the width of the card.", | |
| "Brief.", | |
| "Haiku is an open-source operating system that specifically targets " | |
| "personal computing. It is inspired by the Be Operating System (BeOS), " | |
| "which was known for its clean design and responsive interface. The goal " | |
| "of Haiku is to create a fast, efficient, and easy-to-use desktop " | |
| "operating system that preserves the spirit of BeOS.", | |
| "Another short one.", | |
| "The BView class is one of the fundamental building blocks of Haiku's " | |
| "interface toolkit. It represents a rectangular area on screen and can " | |
| "handle drawing, mouse events, and keyboard events. Views can be nested " | |
| "to create complex user interfaces.", | |
| "Test.", | |
| "Scroll views are essential for displaying content that exceeds the " | |
| "available screen space. They provide scrollbars that allow users to " | |
| "navigate through the content. In Haiku, the BScrollView class wraps " | |
| "another view to provide this functionality.", | |
| "Cards are a popular UI pattern that groups related information together " | |
| "in a visually distinct container. They typically have a background " | |
| "color, some padding to separate content from edges, and sometimes " | |
| "rounded corners or shadows for depth.", | |
| "Hi!", | |
| "The layout system in Haiku uses a constraint-based approach that makes " | |
| "it easier to create responsive interfaces. The BLayout classes handle " | |
| "the positioning and sizing of child views automatically, reducing the " | |
| "need for manual frame calculations.", | |
| "OK.", | |
| "Text wrapping is an important feature for displaying long passages of " | |
| "text in a constrained space. Without it, text would either overflow " | |
| "the visible area or be truncated, making it difficult to read. " | |
| "BTextView provides built-in support for word wrapping.", | |
| "Simple.", | |
| "When implementing card-based interfaces, it's important to consider " | |
| "the performance implications. Each card is a separate view with its " | |
| "own drawing context and event handling. For small numbers of cards, " | |
| "this is fine, but for hundreds or thousands of cards, more efficient " | |
| "approaches like custom drawing or view recycling may be necessary.", | |
| "Yep.", | |
| "The color scheme of an application can significantly affect its " | |
| "usability and aesthetics. Haiku provides several predefined color " | |
| "constants like B_DOCUMENT_BACKGROUND_COLOR and B_PANEL_BACKGROUND_COLOR " | |
| "that help maintain consistency with the system theme.", | |
| "Hey there!", | |
| "In this demo, we have twenty cards with varying amounts of text to " | |
| "demonstrate how the scroll view and card layout work together. The " | |
| "scroll view automatically adjusts its scrollbars based on the total " | |
| "height of the content.", | |
| "End." | |
| }; | |
| const int kNumCards = 20; | |
| for (int i = 0; i < kNumCards; i++) { | |
| BString name; | |
| name << "Card" << (i + 1); | |
| CardView* card = new CardView(name.String(), texts[i]); | |
| fContentView->AddChild(card); | |
| } | |
| Show(); | |
| } | |
| bool | |
| MainWindow::QuitRequested() | |
| { | |
| be_app->PostMessage(B_QUIT_REQUESTED); | |
| return true; | |
| } | |
| void | |
| MainWindow::MessageReceived(BMessage* message) | |
| { | |
| switch (message->what) { | |
| case kMsgAddCard: | |
| { | |
| const char* dummyText = "This is a dynamically added card. " | |
| "It demonstrates that we can append views to the content area " | |
| "at runtime, and the scroll view will correctly adjust to show " | |
| "the new content at the bottom."; | |
| fContentView->AddCard(dummyText); | |
| fScrollView->Invalidate(); | |
| break; | |
| } | |
| case kMsgInsertCard: | |
| { | |
| const char* dummyText = "This is an inserted card at the top. " | |
| "It pushes all existing cards downward, demonstrating how " | |
| "to insert views dynamically into the beginning of a list."; | |
| fContentView->InsertCard(dummyText); | |
| // Update scroll bar proportions because total height changed | |
| BScrollBar* vScroll = fScrollView->ScrollBar(B_VERTICAL); | |
| if (vScroll != NULL) { | |
| // Scroll all the way to the top so the user can see the new card | |
| vScroll->SetValue(0); | |
| } | |
| fScrollView->Invalidate(); | |
| break; | |
| } | |
| default: | |
| BWindow::MessageReceived(message); | |
| break; | |
| } | |
| } | |
| // ============================================================================ | |
| // CardApp | |
| // ============================================================================ | |
| class CardApp : public BApplication { | |
| public: | |
| CardApp(); | |
| virtual void ReadyToRun(); | |
| }; | |
| CardApp::CardApp() | |
| : | |
| BApplication(kAppSignature) | |
| { | |
| } | |
| void | |
| CardApp::ReadyToRun() | |
| { | |
| new MainWindow(); | |
| } | |
| // ============================================================================ | |
| // main | |
| // ============================================================================ | |
| int | |
| main() | |
| { | |
| CardApp app; | |
| app.Run(); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This version use vertical layout builder for a BTextView without scrollview and one with scrollview. | |
| // Both do text wrapping and height adjustment when window resizes. | |
| #include <Application.h> | |
| #include <LayoutBuilder.h> | |
| #include <ScrollView.h> | |
| #include <TextView.h> | |
| #include <Window.h> | |
| class AutoWrappingTitleView : public BTextView { | |
| public: | |
| AutoWrappingTitleView(const char* name) | |
| : BTextView(name) | |
| { | |
| SetWordWrap(true); | |
| // Force the view to receive FrameResized notifications | |
| SetFlags(Flags() | B_FRAME_EVENTS); | |
| } | |
| void FrameResized(float newWidth, float newHeight) override | |
| { | |
| BTextView::FrameResized(newWidth, newHeight); | |
| // 1. Force the internal text layout engine to adapt to the new horizontal width | |
| BRect textRect(0, 0, newWidth, newHeight); | |
| SetTextRect(textRect); | |
| // 2. Alert the layout manager to run a fresh layout calculation pass | |
| InvalidateLayout(); | |
| } | |
| // Tell the system that this view changes its height depending on its width | |
| bool HasHeightForWidth() override | |
| { | |
| return true; | |
| } | |
| // Calculate exact height requirement based on the provided width | |
| void GetHeightForWidth(float width, float* min, float* max, float* preferred) override | |
| { | |
| // Temporarily adjust text rect to calculate the height for the prospective width | |
| BRect oldRect = TextRect(); | |
| BRect tempRect(0, 0, width, oldRect.Height()); | |
| SetTextRect(tempRect); | |
| float textHeight = TextHeight(0, CountLines() - 1); | |
| SetTextRect(oldRect); // Restore original formatting box | |
| // Include tiny buffer padding so text doesn't touch the borders | |
| float computedHeight = textHeight + 12.0f; | |
| if (min) *min = computedHeight; | |
| if (max) *max = computedHeight; | |
| if (preferred) *preferred = computedHeight; | |
| } | |
| }; | |
| class SampleWindow : public BWindow { | |
| public: | |
| SampleWindow(BRect frame) | |
| : BWindow(frame, "Dynamic Title Wrap", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS) | |
| { | |
| // 1. Setup auto-wrapping title text area | |
| fTitleView = new AutoWrappingTitleView("title_view"); | |
| fTitleView->SetText("This is an extra long document title string that will instantly wrap to multiple lines when the user makes the window narrow, pushing down the main content pane."); | |
| fTitleView->MakeEditable(true); | |
| // 2. Setup scrolling body text area | |
| fMainTextView = new BTextView("main_text_view"); | |
| fMainTextView->SetText("This is the main scrollable workspace.\n\nNotice that as you scale the window boundaries, the title box expands/collapses properly while this viewport takes up whatever remainder room is left over."); | |
| fMainTextView->MakeEditable(true); | |
| fMainScroll = new BScrollView("main_scroll", fMainTextView, 0, true, true); | |
| // 3. Construct Layout Assembly | |
| BLayoutBuilder::Group<>(this, B_VERTICAL, 10.0f) | |
| .SetInsets(10.0f) | |
| .Add(fTitleView, 0) // Weight 0 keeps title exactly at its required height | |
| .Add(fMainScroll, 1); // Weight 1 makes scroll area dynamically scale | |
| } | |
| private: | |
| AutoWrappingTitleView* fTitleView; | |
| BTextView* fMainTextView; | |
| BScrollView* fMainScroll; | |
| }; | |
| class SampleApp : public BApplication { | |
| public: | |
| SampleApp() | |
| : BApplication("application/x-vnd.Haiku-TextViewDemo") {} | |
| void ReadyToRun() override { | |
| SampleWindow* window = new SampleWindow(BRect(100, 100, 500, 400)); | |
| window->Show(); | |
| } | |
| }; | |
| int main() { | |
| SampleApp app; | |
| app.Run(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment