Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions native/include/bm.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,28 @@ typedef struct BmScreen {
/* 1 when there are no rows yet because a connection has not finished its first poll: draw a
spinner rather than an empty page. */
int32_t loading;
/* 1 when any row, in view or not, has a mark before its name, and when any has one leading its
detail. Room for each is reserved on every row once any row has one; read from the rows in
view, the columns moved as a row with one scrolled in or out. */
int32_t nameMarks;
int32_t detailMarks;
/* Every distinct first column name across all rows, not only the visible slice, to size the
column from: nameCount build names, then groupNameCount group names, drawn behind an arrow. */
column from: nameCount build names, then groupNameCount group names, drawn behind an arrow.
Then which of those are drawn wider, so each is sized as its row draws it rather than all as
the widest kind: memberNameCount of the names that are a member's, drawn indented under its
group, and markedGroupNameCount of the group names whose row carries a mark. */
const BmString* names;
int32_t nameCount;
int32_t groupNameCount;
int32_t memberNameCount;
int32_t markedGroupNameCount;
/* Every distinct detail across all rows, to size that column from. The chips give way to the
width these want, up to a readable maximum, before the details are cut short. Text only: once
any row's spans carry an icon, add its width and gap to these. */
width these want, up to a readable maximum, before the details are cut short. Text only:
after the detailCount of them come markedDetailCount of those that carry the branch's mark,
to which its width and gap are added. */
const BmString* details;
int32_t detailCount;
int32_t markedDetailCount;
/* Every distinct author across all failed builds, to size the author column from. With none the
column is not drawn. */
const BmString* authors;
Expand Down Expand Up @@ -496,7 +508,7 @@ typedef struct BmInput {
* Bumped whenever the structs above change, or what a field means changes, so a stale native
* library is detected rather than crashed.
*/
#define BM_VERSION 19
#define BM_VERSION 20

/*
* The Swift implementation imports this header for the struct layouts, because Swift does not
Expand Down
103 changes: 60 additions & 43 deletions native/src/bm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -757,32 +757,63 @@ void DrawBuilds(const BmScreen& screen, float bodyHeight) {
} else if (screen.rowCount == 0) {
ImGui::TextColored(dim, "%s", Str(screen, screen.empty).c_str());
} else if (ImGui::BeginTable("rows", 6, flags)) {
const ImGuiStyle& style = ImGui::GetStyle();
// Short of the row's height rather than the sixteen a chip's icon is: the marks carry
// detail, a Jenkins butler's face and the play badge on the Actions mark, that a sixteen
// pixel square turned to a smudge.
const float iconSize = rowHeight - 6.0f;
// Reserved on every row once any row has one, so a group's row, which has no provider
// logo, keeps its name in line with the rows under it, and the names line up where a
// provider gave no repository URL to read a host mark from. Any row of the page rather
// than of those in view, or the columns move as a row with one scrolls in or out.
const bool anyIcon = screen.detailMarks != 0;
const bool anyMark = screen.nameMarks != 0;
const float markWidth = anyMark ? iconSize + style.ItemSpacing.x : 0.0f;

// The name cell starts with a status square a row height wide, then is as wide as the widest
// name across every row, not only those on screen, so it does not shift while scrolling.
// A member is indented by the width of the arrow its group is drawn behind, and the room
// for it is reserved wherever the page has a group at all, open or closed, so the column
// does not shift under the rows as one is opened.
const float indent = screen.groupNameCount > 0 ? ImGui::CalcTextSize("v ").x : 0.0f;
// Each name as the kind of row that shows it: a build's behind the mark, a member's
// indented as well by the width of the arrow its group is drawn behind, and a group's
// behind its arrow, with the mark where its row has one. Every name taken as a member's
// gave the column an indent's width that no row drew in.
// The indent is the open arrow's width, as it is drawn, and a group's name is measured
// apart from it, as it is drawn: sized as the one text "v name", the column was measured
// with a letter the row does not draw, and the name moved as the arrow changed.
const float indent = screen.groupNameCount > 0 ? ImGui::CalcTextSize("▼ ").x : 0.0f;
float nameText = 0.0f;
for (int32_t i = 0; i < screen.nameCount + screen.groupNameCount; i++) {
std::string name = Str(screen, screen.names[i]);
float wanted = indent;
if (i >= screen.nameCount) {
name = "v " + name;
wanted = 0.0f;
const BmString* sized = screen.names;
for (int32_t i = 0; i < screen.nameCount; i++, sized++) {
nameText = std::max(nameText, markWidth + ImGui::CalcTextSize(Str(screen, *sized).c_str()).x);
}

for (int32_t i = 0; i < screen.groupNameCount; i++, sized++) {
nameText = std::max(nameText, indent + ImGui::CalcTextSize(Str(screen, *sized).c_str()).x);
}

// A member's blank cell draws nothing, so it wants no room.
for (int32_t i = 0; i < screen.memberNameCount; i++, sized++) {
if (sized->length > 0) {
nameText = std::max(nameText, markWidth + indent + ImGui::CalcTextSize(Str(screen, *sized).c_str()).x);
}
}

nameText = std::max(nameText, wanted + ImGui::CalcTextSize(name.c_str()).x);
for (int32_t i = 0; i < screen.markedGroupNameCount; i++, sized++) {
nameText = std::max(nameText, markWidth + indent + ImGui::CalcTextSize(Str(screen, *sized).c_str()).x);
}

// The detail cell likewise, up to forty characters: past that a long pipeline or branch is cut
// short rather than pushing every row's chips into the drop down.
// short rather than pushing every row's chips into the drop down. The details are text
// alone, so the branch's mark is added to those that carry one.
const float longestDetail = ImGui::CalcTextSize("0000000000000000000000000000000000000000").x;
float detailText = 0.0f;
for (int32_t i = 0; i < screen.detailCount; i++) {
detailText = std::max(detailText, ImGui::CalcTextSize(Str(screen, screen.details[i]).c_str()).x);
detailText = std::max(detailText, std::min(longestDetail, ImGui::CalcTextSize(Str(screen, screen.details[i]).c_str()).x));
}

detailText = std::min(detailText, ImGui::CalcTextSize("0000000000000000000000000000000000000000").x);
for (int32_t i = 0; i < screen.markedDetailCount; i++) {
const std::string detail = Str(screen, screen.details[screen.detailCount + i]);
detailText = std::max(detailText, SpanIconWidth() + std::min(longestDetail, ImGui::CalcTextSize(detail.c_str()).x));
}

// The author of a failed build, as wide as the widest name up to twenty characters, and no
// width when no failed build names anyone.
Expand All @@ -793,28 +824,6 @@ void DrawBuilds(const BmScreen& screen, float bodyHeight) {

authorWidth = std::min(authorWidth, ImGui::CalcTextSize("00000000000000000000").x);

// Short of the row's height rather than the sixteen a chip's icon is: the marks carry
// detail, a Jenkins butler's face and the play badge on the Actions mark, that a sixteen
// pixel square turned to a smudge.
const float iconSize = rowHeight - 6.0f;
// Reserved on every row once any row has one, so a group's row, which has no provider
// logo, keeps its name in line with the rows under it, and the names line up where a
// provider gave no repository URL to read a host mark from.
bool anyIcon = false;
bool anyMark = false;
for (int32_t i = 0; i < screen.rowCount; i++) {
anyIcon = anyIcon || screen.rows[i].detailIcon.length > 0;
anyMark = anyMark || screen.rows[i].nameIcon.length > 0;
}

// The details are text alone, so the branch's mark is added on top of them once any row
// draws one.
bool anySpanIcon = false;
for (int32_t i = 0; i < screen.spanCount; i++) {
anySpanIcon = anySpanIcon || screen.spans[i].icon.length > 0;
}

const ImGuiStyle& style = ImGui::GetStyle();
// Measured rather than fixed, so each cell holds its widest text at whatever size the font
// was loaded: a countdown past an hour, and the widest set of chips a row carries.
const float barWidth = 104.0f;
Expand All @@ -832,15 +841,19 @@ void DrawBuilds(const BmScreen& screen, float bodyHeight) {
const float overflowWidth = ChipWidth(nullptr, overflowLabel);
// Each boundary between the six columns carries cell padding on both sides of it. What is
// left, the name, the detail, the bar and the chips share.
const float shared = tableWidth - timingWidth - authorWidth - 5.0f * 2.0f * style.CellPadding.x;
const float markWidth = anyMark ? iconSize + style.ItemSpacing.x : 0.0f;
const float nameWanted = rowHeight + style.ItemSpacing.x + markWidth + nameText + 2.0f * style.CellPadding.x;
const float detailWanted = (anyIcon ? iconSize + style.ItemSpacing.x : 0.0f) + (anySpanIcon ? SpanIconWidth() : 0.0f) + detailText;
// A column asked to be no width is still given the table's minimum, which is the frame
// padding. Left out of the sums, the bar's column, hidden, and the author's, with no one
// to name, took that much from the detail, the one column that stretches, and the widest
// detail was cut short by it: "feature/inline" lost its last letter.
const float minimumColumn = std::max(1.0f, style.FramePadding.x);
const float shared = tableWidth - timingWidth - std::max(authorWidth, minimumColumn) - 5.0f * 2.0f * style.CellPadding.x;
const float nameWanted = rowHeight + style.ItemSpacing.x + nameText + 2.0f * style.CellPadding.x;
const float detailWanted = (anyIcon ? iconSize + style.ItemSpacing.x : 0.0f) + detailText;
// The bar gives way before anything else, since the timing beside it says the same: it shows
// only while the names, the detail and every chip still fit. Hidden, its column is kept at no
// width, so the columns after it keep their indexes.
const bool showBar = shared - barWidth - nameWanted - detailWanted >= widestChips;
const float available = showBar ? shared - barWidth : shared;
const float available = showBar ? shared - barWidth : shared - minimumColumn;
// Then the chips: a row without room for all of them puts the last behind an
// overflow chip, rather than the names being cut short. Only once no chip but that one fits
// do the names shrink.
Expand Down Expand Up @@ -929,7 +942,9 @@ void DrawBuilds(const BmScreen& screen, float bodyHeight) {
// arrow already stands where the mark would, and a prefix group, which names no one
// repository, read as an indented heading.
const float rowMarkWidth = (!arrow.empty() && row.nameIcon.length == 0) ? 0.0f : markWidth;
bool nameDrawn = nameLink || rowMarkWidth > 0.0f;
// Nor a group's, which starts the indent after the arrow rather than where whichever
// arrow is drawn happens to end.
bool nameDrawn = nameLink || rowMarkWidth > 0.0f || group;
std::string selectableLabel = (nameDrawn ? arrow : arrow + name) + "##row";
// As tall as the cell, so a click anywhere on the row selects it. A click on a group
// toggles it, so the second press of a double click is dropped, or it would close what
Expand All @@ -948,7 +963,9 @@ void DrawBuilds(const BmScreen& screen, float bodyHeight) {
}

if (nameDrawn) {
float arrowWidth = arrow.empty() ? 0.0f : ImGui::CalcTextSize(arrow.c_str()).x;
// The indent rather than this arrow's own width, which is what the column was
// sized with, so the name does not move as the group opens and its arrow changes.
float arrowWidth = arrow.empty() ? 0.0f : indent;
float markLeft = nameAt.x + arrowWidth;
// The host's mark leads the name, and opens what the name does: it stands for the
// same page, so a click on it is not a click on nothing.
Expand Down
37 changes: 22 additions & 15 deletions native/swift/Sources/Bm/BuildsRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,10 @@ final class BuildsRenderer {
let overflowWidth = chipWidth("", overflowLabel)
// Reserved on every row once any row has one, so a group's row, which has no provider
// logo, keeps its name in line with the rows under it, and the names line up where a
// provider gave no repository URL to read a host mark from.
let iconWidth: CGFloat = frame.rows.contains { !$0.detailIcon.isEmpty } ? logoSize + gap : 0
let markWidth: CGFloat = frame.rows.contains { !$0.nameIcon.isEmpty } ? logoSize + gap : 0
// provider gave no repository URL to read a host mark from. Any row of the page rather
// than of those in view, or the columns move as a row with one scrolls in or out.
let iconWidth: CGFloat = frame.detailMarks ? logoSize + gap : 0
let markWidth: CGFloat = frame.nameMarks ? logoSize + gap : 0
// The author of a failed build, as wide as the widest name up to twenty characters, and gone
// with its gap when no failed build names anyone.
let authorWidth = min(frame.authors.map { measure($0).rounded(.up) }.max() ?? 0, measure(String(repeating: "0", count: 20)))
Expand All @@ -214,18 +215,22 @@ final class BuildsRenderer {
// As wide as the widest name across every row, not only those on screen, so it does not shift
// while scrolling; the detail likewise, up to forty characters, past which a long pipeline or
// branch is cut short rather than pushing every row's chips into the drop down.
// A member is indented by the width of the arrow its group is drawn behind, and the room
// for it is reserved wherever the page has a group at all, open or closed, so the column
// does not shift under the rows as one is opened.
// Each name as the kind of row that shows it, by the parts it is drawn from: a build's
// behind the mark, a member's indented as well by the width of the arrow its group is
// drawn behind, and a group's behind that arrow, with the mark where its row has one. A
// member's blank cell draws nothing, so it wants no room. Every name taken as a member's
// gave the column an indent's width that no row drew in.
let indent: CGFloat = frame.groupNames.isEmpty ? 0 : measure("▼ ").rounded(.up)
let nameWanted = markWidth + max(
indent + (frame.names.map { measure($0).rounded(.up) }.max() ?? 0),
frame.groupNames.map { measure("▼ " + $0).rounded(.up) }.max() ?? 0)
// The details are text alone, so the branch's mark is added on top once any row draws one.
let spanIcons: CGFloat = frame.rows.contains { $0.spans.contains { !$0.icon.isEmpty } } ? spanIconWidth : 0
let detailWanted = iconWidth + spanIcons + min(
frame.details.map { measure($0).rounded(.up) }.max() ?? 0,
measure(String(repeating: "0", count: 40)))
let nameWanted: CGFloat = max(
frame.names.map { markWidth + measure($0).rounded(.up) }.max() ?? 0,
frame.memberNames.filter { !$0.isEmpty }.map { markWidth + indent + measure($0).rounded(.up) }.max() ?? 0,
frame.groupNames.map { indent + measure($0).rounded(.up) }.max() ?? 0,
frame.markedGroupNames.map { markWidth + indent + measure($0).rounded(.up) }.max() ?? 0)
// The details are text alone, so the branch's mark is added to those that carry one.
let longestDetail = measure(String(repeating: "0", count: 40))
let detailWanted: CGFloat = iconWidth + max(
frame.details.map { min(measure($0).rounded(.up), longestDetail) }.max() ?? 0,
frame.markedDetails.map { spanIconWidth + min(measure($0).rounded(.up), longestDetail) }.max() ?? 0)
// The bar gives way before anything else, since the timing beside it says the same: it shows
// only while the names, the detail and every chip still fit.
let showBar = shared - barWidth - gap - nameWanted - detailWanted >= widestChips
Expand Down Expand Up @@ -279,7 +284,9 @@ final class BuildsRenderer {
let arrow = groupArrow(row)
if !arrow.isEmpty {
drawText(arrow, at: CGPoint(x: nameX, y: textY), font: font, colour: Palette.text, width: nameRoom)
let arrowWidth = min(measure(arrow).rounded(.up), nameRoom)
// The indent rather than this arrow's own width, which is what the column was
// sized with, so the name does not move as the group opens and its arrow changes.
let arrowWidth = min(indent, nameRoom)
nameX += arrowWidth
nameRoom -= arrowWidth
}
Expand Down
Loading
Loading