HaikuDepot: package view availability ui

This change will modify the views showing package
detail so that data being not available is presented
in a consistent style. It will also show when data
is loading.

Change-Id: I182070ac1d22f5c75204d46b8668267942740f36
Reviewed-on: https://review.haiku-os.org/c/haiku/+/10939
Reviewed-by: Jérôme Duval <[email protected]>
This commit is contained in:
Andrew Lindesay
2026-05-06 11:39:59 +00:00
parent 329f76a941
commit 76c24b88d1
4 changed files with 254 additions and 37 deletions
+1
View File
@@ -140,6 +140,7 @@ local applicationSources =
MainWindow.cpp MainWindow.cpp
MarkupTextView.cpp MarkupTextView.cpp
Model.cpp Model.cpp
NotAvailableStringView.cpp
PackageContentsView.cpp PackageContentsView.cpp
PackageInfoListener.cpp PackageInfoListener.cpp
PackageInfoView.cpp PackageInfoView.cpp
@@ -0,0 +1,63 @@
/*
* Copyright 2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "NotAvailableStringView.h"
#include <ControlLook.h>
#include "Logger.h"
NotAvailableStringView::NotAvailableStringView(const char* name, const char* text)
:
BView(name, B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW),
fText(text)
{
}
NotAvailableStringView::~NotAvailableStringView()
{
}
BSize
NotAvailableStringView::MaxSize()
{
return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
// can be any size
}
BAlignment
NotAvailableStringView::LayoutAlignment()
{
return BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE);
}
void
NotAvailableStringView::SetText(const char* text)
{
if (fText != text) {
fText = text;
Invalidate();
}
}
const char*
NotAvailableStringView::Text() const
{
return fText;
}
void
NotAvailableStringView::Draw(BRect updateRect)
{
rgb_color highColor = HighColor();
be_control_look->DrawLabel(this, fText, Bounds(), updateRect, ViewColor(),
BControlLook::B_DISABLED, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE), &highColor);
}
@@ -0,0 +1,36 @@
/*
* Copyright 2026, Andrew Lindesay <[email protected]>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#ifndef NOT_AVAILABLE_STRING_VIEW_H
#define NOT_AVAILABLE_STRING_VIEW_H
#include <View.h>
/*! This view abstracts a message to indicate that there is no item to view.
* This exists because the BStringView only renders at the bottom of the
* view frame according to the legacy Be documentation. This is unfortunately
* not convenient for this use-case. In addition, this view will centralize
* the configuration of colors etc... for this purpose.
*/
class NotAvailableStringView : public BView {
public:
NotAvailableStringView(const char* name, const char* text);
virtual ~NotAvailableStringView();
virtual BSize MaxSize();
virtual BAlignment LayoutAlignment();
void SetText(const char* text);
const char* Text() const;
virtual void Draw(BRect bounds);
private:
BString fText;
};
#endif // NOT_AVAILABLE_STRING_VIEW_H
+141 -24
View File
@@ -36,11 +36,13 @@
#include "BitmapView.h" #include "BitmapView.h"
#include "GeneralContentScrollView.h" #include "GeneralContentScrollView.h"
#include "InterfaceDefs.h"
#include "LinkView.h" #include "LinkView.h"
#include "LinkedBitmapView.h" #include "LinkedBitmapView.h"
#include "LocaleUtils.h" #include "LocaleUtils.h"
#include "Logger.h" #include "Logger.h"
#include "MarkupTextView.h" #include "MarkupTextView.h"
#include "NotAvailableStringView.h"
#include "PackageContentsView.h" #include "PackageContentsView.h"
#include "PackageInfo.h" #include "PackageInfo.h"
#include "PackageManager.h" #include "PackageManager.h"
@@ -64,6 +66,12 @@ enum {
}; };
enum {
CARD_AVAILABLE = 0,
CARD_NOT_AVAILABLE = 1
};
static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f; static const float kContentTint = (B_NO_TINT + B_LIGHTEN_1_TINT) / 2.0f;
static const uint32 kScreenshotSize = 320; static const uint32 kScreenshotSize = 320;
@@ -907,10 +915,12 @@ public:
SetHighColor(color); SetHighColor(color);
StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom()); StrokeLine(Bounds().LeftBottom(), Bounds().RightBottom());
} }
}; };
/*! This class will draw a little graphic of the relative count of the ratings
that have been assigned to the package.
*/
class RatingSummaryView : public BGridView { class RatingSummaryView : public BGridView {
public: public:
RatingSummaryView() RatingSummaryView()
@@ -989,25 +999,40 @@ private:
}; };
/*! This view renders the ratings information for the package. It will appear
when the user chooses the "ratings" tab.
*/
class UserRatingsView : public BGroupView { class UserRatingsView : public BGroupView {
public: public:
UserRatingsView() UserRatingsView()
: :
BGroupView("package ratings view", B_HORIZONTAL) BGroupView("package ratings view", B_HORIZONTAL)
{ {
fPackageName = "";
fAmPopulating = false;
SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
fRatingSummaryView = new RatingSummaryView(); fRatingSummaryView = new RatingSummaryView();
ScrollableGroupView* ratingsContainerView = new ScrollableGroupView(); ScrollableGroupView* ratingsContainerView = new ScrollableGroupView();
ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, ratingsContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
kContentTint);
fRatingContainerLayout = ratingsContainerView->GroupLayout(); fRatingContainerLayout = ratingsContainerView->GroupLayout();
BScrollView* scrollView BScrollView* scrollView
= new RatingsScrollView("ratings scroll view", ratingsContainerView); = new RatingsScrollView("ratings scroll view", ratingsContainerView);
scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
fNoRatingsView = new NotAvailableStringView(
"no ratings",B_TRANSLATE("No user ratings available."));
fNoRatingsView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
// This allows for switching between the "no ratings" and the
// list of ratings views if there are any.
fRatingContainerCardView = new BCardView();
fRatingContainerCardView->AddChild(scrollView);
fRatingContainerCardView->AddChild(fNoRatingsView);
BLayoutBuilder::Group<>(this) BLayoutBuilder::Group<>(this)
.AddGroup(B_VERTICAL) .AddGroup(B_VERTICAL)
.Add(fRatingSummaryView, 0.0f) .Add(fRatingSummaryView, 0.0f)
@@ -1015,7 +1040,7 @@ public:
.SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f) .SetInsets(0.0f, B_USE_DEFAULT_SPACING, 0.0f, 0.0f)
.End() .End()
.AddStrut(64.0) .AddStrut(64.0)
.Add(scrollView, 1.0f) .Add(fRatingContainerCardView, 1.0f)
.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f); .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f);
} }
@@ -1041,14 +1066,11 @@ public:
if (userRatingInfo.IsSet()) if (userRatingInfo.IsSet())
count = userRatingInfo->CountUserRatings(); count = userRatingInfo->CountUserRatings();
BString packageName = package.IsSet() ? package->Name() : "";
fAmPopulating = fAmPopulating && (packageName == fPackageName) && count == 0;
if (count == 0) { if (count == 0) {
BStringView* noRatingsView _SetNotAvailable();
= new BStringView("no ratings", B_TRANSLATE("No user ratings available."));
noRatingsView->SetViewUIColor(ViewUIColor(), kContentTint);
noRatingsView->SetAlignment(B_ALIGN_CENTER);
noRatingsView->SetHighColor(disable_color(ui_color(B_PANEL_TEXT_COLOR), ViewColor()));
noRatingsView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
fRatingContainerLayout->AddView(0, noRatingsView);
} else { } else {
for (int i = count - 1; i >= 0; i--) { for (int i = count - 1; i >= 0; i--) {
UserRatingRef rating = userRatingInfo->UserRatingAtIndex(i); UserRatingRef rating = userRatingInfo->UserRatingAtIndex(i);
@@ -1058,15 +1080,18 @@ public:
RatingItemView* view = new RatingItemView(rating); RatingItemView* view = new RatingItemView(rating);
fRatingContainerLayout->AddView(0, view); fRatingContainerLayout->AddView(0, view);
} }
fRatingContainerCardView->CardLayout()->SetVisibleItem(CARD_AVAILABLE);
} }
InvalidateLayout(); InvalidateLayout();
fPackageName = packageName;
} }
void Clear() void Clear()
{ {
fRatingSummaryView->Clear(); fAmPopulating = false;
ClearRatings(); _SetNotAvailable();
} }
void ClearRatings() void ClearRatings()
@@ -1074,8 +1099,6 @@ public:
for (int32 i = fRatingContainerLayout->CountItems() - 1; for (int32 i = fRatingContainerLayout->CountItems() - 1;
BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) { BLayoutItem* item = fRatingContainerLayout->ItemAt(i); i--) {
BView* view = dynamic_cast<RatingItemView*>(item->View()); BView* view = dynamic_cast<RatingItemView*>(item->View());
if (view == NULL)
view = dynamic_cast<BStringView*>(item->View());
if (view != NULL) { if (view != NULL) {
view->RemoveSelf(); view->RemoveSelf();
delete view; delete view;
@@ -1083,9 +1106,32 @@ public:
} }
} }
void _SetNotAvailable() {
fNoRatingsView->SetText(_NotAvailableText());
fRatingContainerCardView->CardLayout()->SetVisibleItem(CARD_NOT_AVAILABLE);
fRatingSummaryView->Clear();
ClearRatings();
}
// TODO; need some way to signal failure.
void SetAmPopulating() {
fAmPopulating = true;
_SetNotAvailable();
}
const char* _NotAvailableText() {
if (fAmPopulating)
return B_TRANSLATE("User ratings loading" B_UTF8_ELLIPSIS);
return B_TRANSLATE("No user ratings available.");
}
private: private:
BString fPackageName;
bool fAmPopulating;
NotAvailableStringView* fNoRatingsView;
BGroupLayout* fRatingContainerLayout; BGroupLayout* fRatingContainerLayout;
RatingSummaryView* fRatingSummaryView; RatingSummaryView* fRatingSummaryView;
BCardView* fRatingContainerCardView;
}; };
@@ -1101,8 +1147,15 @@ public:
SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
fPackageContents = new PackageContentsView("contents_list"); fPackageContents = new PackageContentsView("contents_list");
AddChild(fPackageContents);
fNotAvailableView = new NotAvailableStringView("no contents",
B_TRANSLATE("No contents available."));
fCardView = new BCardView();
fCardView->AddChild(fPackageContents);
fCardView->AddChild(fNotAvailableView);
AddChild(fCardView);
} }
virtual ~ContentsView() virtual ~ContentsView()
@@ -1115,15 +1168,26 @@ public:
void SetPackage(const PackageInfoRef package) void SetPackage(const PackageInfoRef package)
{ {
// if the package is not installed and is not a local file on disk then
// there is no point in attempting to populate data for it.
if (PackageUtils::IsActivatedOrLocalFile(package)) {
fPackageContents->SetPackage(package); fPackageContents->SetPackage(package);
fCardView->CardLayout()->SetVisibleItem(CARD_AVAILABLE);
} else {
Clear();
}
} }
void Clear() void Clear()
{ {
fPackageContents->Clear(); fPackageContents->Clear();
fCardView->CardLayout()->SetVisibleItem(CARD_NOT_AVAILABLE);
} }
private: private:
BCardView* fCardView;
NotAvailableStringView* fNotAvailableView;
PackageContentsView* fPackageContents; PackageContentsView* fPackageContents;
}; };
@@ -1131,24 +1195,37 @@ private:
// #pragma mark - ChangelogView // #pragma mark - ChangelogView
/*! This view appears to show the Changelog when the user chooses the
changelog tab.
*/
class ChangelogView : public BGroupView { class ChangelogView : public BGroupView {
public: public:
ChangelogView() ChangelogView()
: :
BGroupView("package changelog view", B_HORIZONTAL) BGroupView("package changelog view", B_HORIZONTAL)
{ {
fAmPopulating = false;
fPackageName = "";
SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint); SetViewUIColor(B_PANEL_BACKGROUND_COLOR, kContentTint);
fTextView = new MarkupTextView("changelog view"); fTextView = new MarkupTextView("changelog view");
fTextView->SetLowUIColor(ViewUIColor()); fTextView->SetLowUIColor(ViewUIColor());
fTextView->SetInsets(be_plain_font->Size()); fTextView->SetInsets(be_plain_font->Size());
fTextScrollView = new GeneralContentScrollView("changelog scroll view", fTextView);
BScrollView* scrollView = new GeneralContentScrollView("changelog scroll view", fTextView); fNotAvailableView = new NotAvailableStringView("no changelog", _NotAvailableText());
fCardView = new BCardView();
fCardView->AddChild(fTextScrollView);
fCardView->AddChild(fNotAvailableView);
BLayoutBuilder::Group<>(this) BLayoutBuilder::Group<>(this)
.Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f)) .Add(BSpaceLayoutItem::CreateHorizontalStrut(32.0f))
.Add(scrollView, 1.0f) .Add(fCardView, 1.0f)
.SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f); .SetInsets(B_USE_DEFAULT_SPACING, -1.0f, -1.0f, -1.0f);
Clear();
} }
virtual ~ChangelogView() virtual ~ChangelogView()
@@ -1161,6 +1238,11 @@ public:
void SetPackage(const PackageInfoRef package) void SetPackage(const PackageInfoRef package)
{ {
if (package.IsSet())
HDDEBUG("setting package on changelog view [%s]", package->Name().String());
else
HDDEBUG("setting package on changelog view to empty package");
PackageLocalizedTextRef localizedText = package->LocalizedText(); PackageLocalizedTextRef localizedText = package->LocalizedText();
BString changelog; BString changelog;
@@ -1169,19 +1251,52 @@ public:
changelog = localizedText->Changelog(); changelog = localizedText->Changelog();
} }
if (changelog.Length() > 0) BString packageName = package.IsSet() ? package->Name() : "";
fAmPopulating = fAmPopulating && (packageName == fPackageName) && changelog.Length() == 0;
if (changelog.Length() > 0) {
fTextView->SetText(changelog); fTextView->SetText(changelog);
else fCardView->CardLayout()->SetVisibleItem(CARD_AVAILABLE);
fTextView->SetDisabledText(B_TRANSLATE("No changelog available.")); } else {
_SetNotAvailable();
}
fPackageName = packageName;
}
// TODO; need some way to signal failure.
void SetAmPopulating() {
HDDEBUG("set am populating changelog view");
fAmPopulating = true;
_SetNotAvailable();
} }
void Clear() void Clear()
{ {
HDDEBUG("clearing changelog view");
fAmPopulating = false;
_SetNotAvailable();
}
void _SetNotAvailable() {
fNotAvailableView->SetText(_NotAvailableText());
fCardView->CardLayout()->SetVisibleItem(CARD_NOT_AVAILABLE);
fTextView->SetText(""); fTextView->SetText("");
} }
const char* _NotAvailableText() {
if (fAmPopulating)
return B_TRANSLATE("Changelog loading" B_UTF8_ELLIPSIS);
return B_TRANSLATE("No changelog available.");
}
private: private:
BString fPackageName;
MarkupTextView* fTextView; MarkupTextView* fTextView;
BCardView* fCardView;
NotAvailableStringView* fNotAvailableView;
BScrollView* fTextScrollView;
bool fAmPopulating;
}; };
@@ -1359,6 +1474,8 @@ private:
PopulateChangelogPackageAction action(package->Name()); PopulateChangelogPackageAction action(package->Name());
BMessage message = action.Message(); BMessage message = action.Message();
Window()->PostMessage(&message); Window()->PostMessage(&message);
fChangelogView->SetAmPopulating();
} }
void _MaybePopulateUserRatings(const PackageInfoRef package) { void _MaybePopulateUserRatings(const PackageInfoRef package) {
@@ -1386,6 +1503,8 @@ private:
PopulateUserRatingsPackageAction action(package->Name()); PopulateUserRatingsPackageAction action(package->Name());
BMessage message = action.Message(); BMessage message = action.Message();
Window()->PostMessage(&message); Window()->PostMessage(&message);
fUserRatingsView->SetAmPopulating();
} }
private: private:
@@ -1436,9 +1555,7 @@ PackageInfoView::PackageInfoView(Model* model)
.AddGroup(B_HORIZONTAL, 0.0f) .AddGroup(B_HORIZONTAL, 0.0f)
.Add(fTitleView, 6.0f) .Add(fTitleView, 6.0f)
.Add(fPackageActionView, 1.0f) .Add(fPackageActionView, 1.0f)
.SetInsets( .SetInsets(B_USE_DEFAULT_SPACING, 0.0f, B_USE_DEFAULT_SPACING, 0.0f)
B_USE_DEFAULT_SPACING, 0.0f,
B_USE_DEFAULT_SPACING, 0.0f)
.End() .End()
.Add(fPagesView); .Add(fPagesView);