finally
This commit is contained in:
parent
cfafcbbbe7
commit
f03ca4f293
1 changed files with 140 additions and 140 deletions
280
src/main.cpp
280
src/main.cpp
|
@ -4,6 +4,7 @@
|
||||||
#include <ftxui/dom/node.hpp> // ftxui::{Render}
|
#include <ftxui/dom/node.hpp> // ftxui::{Render}
|
||||||
#include <ftxui/screen/color.hpp> // ftxui::Color
|
#include <ftxui/screen/color.hpp> // ftxui::Color
|
||||||
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
|
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
|
||||||
|
#include <ftxui/screen/string.hpp> // ftxui::string_width
|
||||||
#include <print> // std::println
|
#include <print> // std::println
|
||||||
#include <ranges> // std::ranges::{iota, to, transform}
|
#include <ranges> // std::ranges::{iota, to, transform}
|
||||||
|
|
||||||
|
@ -18,8 +19,6 @@ namespace ui {
|
||||||
using ftxui::Color;
|
using ftxui::Color;
|
||||||
using util::types::StringView, util::types::i32;
|
using util::types::StringView, util::types::i32;
|
||||||
|
|
||||||
static constexpr i32 MAX_PARAGRAPH_LENGTH = 30;
|
|
||||||
|
|
||||||
// Color themes
|
// Color themes
|
||||||
struct Theme {
|
struct Theme {
|
||||||
Color::Palette16 icon;
|
Color::Palette16 icon;
|
||||||
|
@ -110,26 +109,29 @@ namespace {
|
||||||
using namespace util::logging;
|
using namespace util::logging;
|
||||||
using namespace ftxui;
|
using namespace ftxui;
|
||||||
|
|
||||||
// Helper struct to hold row data before calculating max width
|
|
||||||
struct RowInfo {
|
struct RowInfo {
|
||||||
StringView icon;
|
StringView icon;
|
||||||
StringView label;
|
StringView label;
|
||||||
String value; // Store the final formatted value as String
|
String value;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn CreateColorCircles() -> Element {
|
fn CreateColorCircles() -> Element {
|
||||||
return hbox(
|
return hbox(
|
||||||
std::views::iota(0, 16) | std::views::transform([](ui::i32 colorIndex) {
|
std::views::iota(0, 16) | std::views::transform([](i32 colorIndex) {
|
||||||
return hbox({ text("◯") | bold | color(static_cast<Color::Palette256>(colorIndex)), text(" ") });
|
return hbox({ text("◯") | bold | color(static_cast<Color::Palette256>(colorIndex)), text(" ") });
|
||||||
}) |
|
}) |
|
||||||
std::ranges::to<Elements>()
|
std::ranges::to<Elements>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_visual_width(const String& str) -> usize { return ftxui::string_width(str); }
|
||||||
|
fn get_visual_width_sv(const StringView& sview) -> usize { return ftxui::string_width(String(sview)); }
|
||||||
|
|
||||||
fn find_max_label_len(const std::vector<RowInfo>& rows) -> usize {
|
fn find_max_label_len(const std::vector<RowInfo>& rows) -> usize {
|
||||||
usize max_len = 0;
|
usize maxWidth = 0;
|
||||||
for (const auto& row : rows) { max_len = std::max(max_len, row.label.length()); }
|
for (const RowInfo& row : rows) maxWidth = std::max(maxWidth, get_visual_width_sv(row.label));
|
||||||
return max_len;
|
|
||||||
|
return maxWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn SystemInfoBox(const Config& config, const os::SystemData& data) -> Element {
|
fn SystemInfoBox(const Config& config, const os::SystemData& data) -> Element {
|
||||||
|
@ -139,135 +141,153 @@ namespace {
|
||||||
const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, packageIcon, deIcon, wmIcon] =
|
const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, packageIcon, deIcon, wmIcon] =
|
||||||
ui::ICON_TYPE;
|
ui::ICON_TYPE;
|
||||||
|
|
||||||
// --- Stage 1: Collect data for rows into logical sections ---
|
std::vector<RowInfo> initialRows; // Date, Weather
|
||||||
std::vector<RowInfo> initial_rows; // Date, Weather
|
std::vector<RowInfo> systemInfoRows; // Host, Kernel, OS, RAM, Disk, Shell, Packages
|
||||||
std::vector<RowInfo> system_info_rows; // Host, Kernel, OS, RAM, Disk, Shell, Packages
|
std::vector<RowInfo> envInfoRows; // DE, WM
|
||||||
std::vector<RowInfo> env_info_rows; // DE, WM
|
|
||||||
|
|
||||||
// --- Section 1: Date and Weather ---
|
if (data.date)
|
||||||
if (data.date) {
|
initialRows.push_back({ calendarIcon, "Date", *data.date });
|
||||||
initial_rows.push_back({ calendarIcon, "Date", *data.date });
|
else
|
||||||
} else {
|
|
||||||
debug_at(data.date.error());
|
debug_at(data.date.error());
|
||||||
}
|
|
||||||
if (weather.enabled && data.weather) {
|
if (weather.enabled && data.weather) {
|
||||||
const weather::Output& weatherInfo = *data.weather;
|
const weather::Output& weatherInfo = *data.weather;
|
||||||
String weatherValue = weather.showTownName
|
String weatherValue = weather.showTownName
|
||||||
? std::format("{}°F in {}", std::lround(weatherInfo.main.temp), weatherInfo.name)
|
? std::format("{}°F in {}", std::lround(weatherInfo.main.temp), weatherInfo.name)
|
||||||
: std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description);
|
: std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description);
|
||||||
initial_rows.push_back({ weatherIcon, "Weather", std::move(weatherValue) });
|
initialRows.push_back({ weatherIcon, "Weather", std::move(weatherValue) });
|
||||||
} else if (weather.enabled) {
|
} else if (weather.enabled)
|
||||||
debug_at(data.weather.error());
|
debug_at(data.weather.error());
|
||||||
}
|
|
||||||
|
|
||||||
// --- Section 2: Core System Info ---
|
if (data.host && !data.host->empty())
|
||||||
if (data.host && !data.host->empty()) {
|
systemInfoRows.push_back({ hostIcon, "Host", *data.host });
|
||||||
system_info_rows.push_back({ hostIcon, "Host", *data.host });
|
else
|
||||||
} else {
|
|
||||||
debug_at(data.host.error());
|
debug_at(data.host.error());
|
||||||
}
|
|
||||||
if (data.kernelVersion) {
|
if (data.kernelVersion)
|
||||||
system_info_rows.push_back({ kernelIcon, "Kernel", *data.kernelVersion });
|
systemInfoRows.push_back({ kernelIcon, "Kernel", *data.kernelVersion });
|
||||||
} else {
|
else
|
||||||
debug_at(data.kernelVersion.error());
|
debug_at(data.kernelVersion.error());
|
||||||
}
|
|
||||||
if (data.osVersion) {
|
if (data.osVersion)
|
||||||
system_info_rows.push_back({ osIcon, "OS", *data.osVersion });
|
systemInfoRows.push_back({ osIcon, "OS", *data.osVersion });
|
||||||
} else {
|
else
|
||||||
debug_at(data.osVersion.error());
|
debug_at(data.osVersion.error());
|
||||||
}
|
|
||||||
if (data.memInfo) {
|
if (data.memInfo)
|
||||||
system_info_rows.push_back({ memoryIcon, "RAM", std::format("{}", BytesToGiB { *data.memInfo }) });
|
systemInfoRows.push_back({ memoryIcon, "RAM", std::format("{}", BytesToGiB { *data.memInfo }) });
|
||||||
} else {
|
else
|
||||||
debug_at(data.memInfo.error());
|
debug_at(data.memInfo.error());
|
||||||
}
|
|
||||||
if (data.diskUsage) {
|
if (data.diskUsage)
|
||||||
system_info_rows.push_back(
|
systemInfoRows.push_back(
|
||||||
{ diskIcon,
|
{ diskIcon,
|
||||||
"Disk",
|
"Disk",
|
||||||
std::format("{}/{}", BytesToGiB { data.diskUsage->used_bytes }, BytesToGiB { data.diskUsage->total_bytes }) }
|
std::format("{}/{}", BytesToGiB { data.diskUsage->used_bytes }, BytesToGiB { data.diskUsage->total_bytes }) }
|
||||||
);
|
);
|
||||||
} else {
|
else
|
||||||
debug_at(data.diskUsage.error());
|
debug_at(data.diskUsage.error());
|
||||||
}
|
|
||||||
if (data.shell) {
|
if (data.shell)
|
||||||
system_info_rows.push_back({ shellIcon, "Shell", *data.shell });
|
systemInfoRows.push_back({ shellIcon, "Shell", *data.shell });
|
||||||
} else {
|
else
|
||||||
debug_at(data.shell.error());
|
debug_at(data.shell.error());
|
||||||
}
|
|
||||||
if (data.packageCount) {
|
if (data.packageCount) {
|
||||||
if (*data.packageCount > 0) {
|
if (*data.packageCount > 0)
|
||||||
system_info_rows.push_back({ packageIcon, "Packages", std::format("{}", *data.packageCount) });
|
systemInfoRows.push_back({ packageIcon, "Packages", std::format("{}", *data.packageCount) });
|
||||||
} else {
|
else
|
||||||
debug_log("Package count is 0, skipping");
|
debug_log("Package count is 0, skipping");
|
||||||
}
|
} else
|
||||||
} else {
|
|
||||||
debug_at(data.packageCount.error());
|
debug_at(data.packageCount.error());
|
||||||
}
|
|
||||||
|
|
||||||
// --- Section 3: Desktop Env / Window Manager ---
|
bool addedDe = false;
|
||||||
bool added_de = false;
|
|
||||||
if (data.desktopEnv && (!data.windowMgr || *data.desktopEnv != *data.windowMgr)) {
|
if (data.desktopEnv && (!data.windowMgr || *data.desktopEnv != *data.windowMgr)) {
|
||||||
env_info_rows.push_back({ deIcon, "DE", *data.desktopEnv });
|
envInfoRows.push_back({ deIcon, "DE", *data.desktopEnv });
|
||||||
added_de = true;
|
addedDe = true;
|
||||||
} else if (!data.desktopEnv) { /* Optional debug */
|
} else if (!data.desktopEnv)
|
||||||
}
|
debug_at(data.desktopEnv.error());
|
||||||
if (data.windowMgr) {
|
|
||||||
if (!added_de || (data.desktopEnv && *data.desktopEnv != *data.windowMgr)) {
|
if (data.windowMgr) {
|
||||||
env_info_rows.push_back({ wmIcon, "WM", *data.windowMgr });
|
if (!addedDe || (data.desktopEnv && *data.desktopEnv != *data.windowMgr))
|
||||||
}
|
envInfoRows.push_back({ wmIcon, "WM", *data.windowMgr });
|
||||||
} else {
|
} else
|
||||||
debug_at(data.windowMgr.error());
|
debug_at(data.windowMgr.error());
|
||||||
}
|
|
||||||
|
bool nowPlayingActive = false;
|
||||||
|
String npText;
|
||||||
|
|
||||||
// --- Section 4: Now Playing (Handled separately) ---
|
|
||||||
bool now_playing_active = false;
|
|
||||||
String np_text;
|
|
||||||
if (config.nowPlaying.enabled && data.nowPlaying) {
|
if (config.nowPlaying.enabled && data.nowPlaying) {
|
||||||
const String title = data.nowPlaying->title.value_or("Unknown Title");
|
const String title = data.nowPlaying->title.value_or("Unknown Title");
|
||||||
const String artist = data.nowPlaying->artist.value_or("Unknown Artist");
|
const String artist = data.nowPlaying->artist.value_or("Unknown Artist");
|
||||||
np_text = artist + " - " + title;
|
npText = artist + " - " + title;
|
||||||
now_playing_active = true;
|
nowPlayingActive = true;
|
||||||
} else if (config.nowPlaying.enabled) { /* Optional debug */
|
} else if (config.nowPlaying.enabled)
|
||||||
}
|
debug_at(data.nowPlaying.error());
|
||||||
|
|
||||||
// --- Stage 2: Calculate max width needed for Icon + Label across relevant sections ---
|
usize maxContentWidth = 0;
|
||||||
usize maxActualLabelLen = 0;
|
|
||||||
auto find_max_label = [&](const std::vector<RowInfo>& rows) {
|
usize greetingWidth = get_visual_width_sv(userIcon) + get_visual_width_sv("Hello ") + get_visual_width(name) +
|
||||||
usize max_len = 0;
|
get_visual_width_sv("! ");
|
||||||
for (const auto& row : rows) { max_len = std::max(max_len, row.label.length()); }
|
maxContentWidth = std::max(maxContentWidth, greetingWidth);
|
||||||
return max_len;
|
|
||||||
|
usize paletteWidth = get_visual_width_sv(userIcon) + (16 * (get_visual_width_sv("◯") + get_visual_width_sv(" ")));
|
||||||
|
maxContentWidth = std::max(maxContentWidth, paletteWidth);
|
||||||
|
|
||||||
|
usize iconActualWidth = get_visual_width_sv(userIcon);
|
||||||
|
|
||||||
|
usize maxLabelWidthInitial = find_max_label_len(initialRows);
|
||||||
|
usize maxLabelWidthSystem = find_max_label_len(systemInfoRows);
|
||||||
|
usize maxLabelWidthEnv = find_max_label_len(envInfoRows);
|
||||||
|
|
||||||
|
usize requiredWidthInitialW = iconActualWidth + maxLabelWidthInitial;
|
||||||
|
usize requiredWidthSystemW = iconActualWidth + maxLabelWidthSystem;
|
||||||
|
usize requiredWidthEnvW = iconActualWidth + maxLabelWidthEnv;
|
||||||
|
|
||||||
|
fn calculateRowVisualWidth = [&](const RowInfo& row, usize requiredLabelVisualWidth) -> usize {
|
||||||
|
return requiredLabelVisualWidth + get_visual_width(row.value) + get_visual_width_sv(" "); // Use visual width
|
||||||
};
|
};
|
||||||
|
|
||||||
maxActualLabelLen =
|
for (const RowInfo& row : initialRows)
|
||||||
std::max({ find_max_label(initial_rows), find_max_label(system_info_rows), find_max_label(env_info_rows) });
|
maxContentWidth = std::max(maxContentWidth, calculateRowVisualWidth(row, requiredWidthInitialW));
|
||||||
// Note: We don't include "Playing" from Now Playing in this calculation
|
|
||||||
// as it's handled differently, but we could if we wanted perfect alignment.
|
|
||||||
|
|
||||||
// --- Stage 2: Calculate max width needed PER SECTION ---
|
for (const RowInfo& row : systemInfoRows)
|
||||||
// Assume consistent icon width for simplicity (adjust if icons vary significantly)
|
maxContentWidth = std::max(maxContentWidth, calculateRowVisualWidth(row, requiredWidthSystemW));
|
||||||
usize iconLen = ui::ICON_TYPE.user.length() - 1;
|
|
||||||
// Optionally refine iconLen based on actual icons used, if needed
|
|
||||||
|
|
||||||
usize maxLabelLen_initial = find_max_label_len(initial_rows);
|
for (const RowInfo& row : envInfoRows)
|
||||||
usize maxLabelLen_system = find_max_label_len(system_info_rows);
|
maxContentWidth = std::max(maxContentWidth, calculateRowVisualWidth(row, requiredWidthEnvW));
|
||||||
usize maxLabelLen_env = find_max_label_len(env_info_rows);
|
|
||||||
|
|
||||||
usize requiredWidth_initial = iconLen + maxLabelLen_initial;
|
usize targetBoxWidth = maxContentWidth + 2;
|
||||||
usize requiredWidth_system = iconLen + maxLabelLen_system;
|
|
||||||
usize requiredWidth_env = iconLen + maxLabelLen_env;
|
|
||||||
|
|
||||||
// --- Stage 3: Define the row creation function ---
|
usize npFixedWidthLeft = 0;
|
||||||
auto createStandardRow = [&](const RowInfo& row, usize sectionRequiredWidth) {
|
usize npFixedWidthRight = 0;
|
||||||
Element leftPart = hbox(
|
|
||||||
{
|
if (nowPlayingActive) {
|
||||||
text(String(row.icon)) | color(ui::DEFAULT_THEME.icon),
|
npFixedWidthLeft = get_visual_width_sv(musicIcon) + get_visual_width_sv("Playing") + get_visual_width_sv(" ");
|
||||||
text(String(row.label)) | color(ui::DEFAULT_THEME.label),
|
npFixedWidthRight = get_visual_width_sv(" ");
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
i32 paragraphLimit = 1;
|
||||||
|
|
||||||
|
if (nowPlayingActive) {
|
||||||
|
i32 availableForParagraph =
|
||||||
|
static_cast<i32>(targetBoxWidth) - static_cast<i32>(npFixedWidthLeft) - static_cast<i32>(npFixedWidthRight);
|
||||||
|
|
||||||
|
availableForParagraph -= 2;
|
||||||
|
|
||||||
|
paragraphLimit = std::max(1, availableForParagraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createStandardRow = [&](const RowInfo& row, usize sectionRequiredVisualWidth) {
|
||||||
return hbox(
|
return hbox(
|
||||||
{
|
{
|
||||||
leftPart | size(WIDTH, EQUAL, static_cast<int>(sectionRequiredWidth)),
|
hbox(
|
||||||
|
{
|
||||||
|
text(String(row.icon)) | color(ui::DEFAULT_THEME.icon),
|
||||||
|
text(String(row.label)) | color(ui::DEFAULT_THEME.label),
|
||||||
|
}
|
||||||
|
) |
|
||||||
|
size(WIDTH, EQUAL, static_cast<int>(sectionRequiredVisualWidth)),
|
||||||
filler(),
|
filler(),
|
||||||
text(row.value) | color(ui::DEFAULT_THEME.value),
|
text(row.value) | color(ui::DEFAULT_THEME.value),
|
||||||
text(" "),
|
text(" "),
|
||||||
|
@ -275,62 +295,42 @@ namespace {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Stage 4: Build the final Elements list with explicit separators and section-specific widths ---
|
|
||||||
Elements content;
|
Elements content;
|
||||||
|
|
||||||
// Greeting and Palette
|
|
||||||
content.push_back(text(String(userIcon) + "Hello " + name + "! ") | bold | color(Color::Cyan));
|
content.push_back(text(String(userIcon) + "Hello " + name + "! ") | bold | color(Color::Cyan));
|
||||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); // Separator after greeting
|
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||||
content.push_back(hbox({ text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon), CreateColorCircles() }));
|
content.push_back(hbox({ text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon), CreateColorCircles() }));
|
||||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); // Separator after palette
|
|
||||||
|
|
||||||
// Determine section presence
|
bool section1Present = !initialRows.empty();
|
||||||
bool section1_present = !initial_rows.empty();
|
bool section2Present = !systemInfoRows.empty();
|
||||||
bool section2_present = !system_info_rows.empty();
|
bool section3Present = !envInfoRows.empty();
|
||||||
bool section3_present = !env_info_rows.empty();
|
|
||||||
bool section4_present = now_playing_active;
|
|
||||||
|
|
||||||
// Add Section 1 (Date/Weather) - Use initial width
|
if (section1Present)
|
||||||
for (const auto& row : initial_rows) { content.push_back(createStandardRow(row, requiredWidth_initial)); }
|
|
||||||
|
|
||||||
// Separator before Section 2?
|
|
||||||
if (section1_present && (section2_present || section3_present || section4_present)) {
|
|
||||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||||
}
|
|
||||||
|
|
||||||
// Add Section 2 (System Info) - Use system width
|
for (const RowInfo& row : initialRows) content.push_back(createStandardRow(row, requiredWidthInitialW));
|
||||||
for (const auto& row : system_info_rows) { content.push_back(createStandardRow(row, requiredWidth_system)); }
|
|
||||||
|
|
||||||
// Separator before Section 3?
|
if ((section1Present && (section2Present || section3Present)) || (!section1Present && section2Present))
|
||||||
if (section2_present && (section3_present || section4_present)) {
|
|
||||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||||
}
|
|
||||||
|
|
||||||
// Add Section 3 (DE/WM) - Use env width
|
for (const RowInfo& row : systemInfoRows) content.push_back(createStandardRow(row, requiredWidthSystemW));
|
||||||
for (const auto& row : env_info_rows) { content.push_back(createStandardRow(row, requiredWidth_env)); }
|
|
||||||
|
|
||||||
// Separator before Section 4?
|
if (section2Present && section3Present)
|
||||||
if (section3_present && section4_present) {
|
|
||||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||||
} else if (!section3_present && (section1_present || section2_present) && section4_present) {
|
|
||||||
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Section 4 (Now Playing)
|
for (const RowInfo& row : envInfoRows) content.push_back(createStandardRow(row, requiredWidthEnvW));
|
||||||
if (section4_present) {
|
|
||||||
// Pad "Playing" label based on the max label length of the preceding section (Env)
|
if ((section1Present || section2Present || section3Present) && nowPlayingActive)
|
||||||
usize playingLabelPadding = maxLabelLen_env;
|
content.push_back(separator() | color(ui::DEFAULT_THEME.border));
|
||||||
|
|
||||||
|
if (nowPlayingActive) {
|
||||||
content.push_back(hbox(
|
content.push_back(hbox(
|
||||||
{
|
{ text(String(musicIcon)) | color(ui::DEFAULT_THEME.icon),
|
||||||
text(String(musicIcon)) | color(ui::DEFAULT_THEME.icon),
|
text("Playing") | color(ui::DEFAULT_THEME.label),
|
||||||
// Pad only the label part
|
|
||||||
hbox({ text("Playing") | color(ui::DEFAULT_THEME.label) }) |
|
|
||||||
size(WIDTH, EQUAL, static_cast<int>(playingLabelPadding)),
|
|
||||||
text(" "), // Space after label
|
|
||||||
filler(),
|
|
||||||
paragraph(np_text) | color(Color::Magenta) | size(WIDTH, LESS_THAN, ui::MAX_PARAGRAPH_LENGTH),
|
|
||||||
text(" "),
|
text(" "),
|
||||||
}
|
filler(),
|
||||||
|
paragraphAlignRight(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, paragraphLimit),
|
||||||
|
text(" ") }
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue