Add a Network Widget

This new widget visualizes the up and download speeds for a
specified network adapter. It is on by default, but can be disabled.
This commit is contained in:
scorpion-26 2023-02-10 17:20:26 +01:00
parent c901b59ab4
commit 3134c82245
12 changed files with 446 additions and 6 deletions

View file

@ -141,6 +141,60 @@
font-size: 16px;
}
.network-data-text {
color: #50fa7b;
margin-right: 6px;
font-size: 16px;
}
.network-up-under {
color: #44475a;
}
.network-up-low {
color: #50fa7b;
}
.network-up-mid-low {
color: #f1fa8c;
}
.network-up-mid-high {
color: #ffb86c;
}
.network-up-high {
color: #bd93f9;
}
.network-up-over {
color: #ff5555;
}
.network-down-under {
color: #44475a;
}
.network-down-low {
color: #50fa7b;
}
.network-down-mid-low {
color: #f1fa8c;
}
.network-down-mid-high {
color: #ffb86c;
}
.network-down-high {
color: #bd93f9;
}
.network-down-over {
color: #ff5555;
}
.ws-dead {
color: #44475a;
font-size: 16px;

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAmBA;EACI;EACA;;;AASJ;EACI,kBA7BC;EA8BD;;;AAGJ;EACI;EACA;;;AAGJ;EACI,WAxBO;;;AA2BX;EACI;EAEA;EAEA,OA5CO;;;AA8CX;EACI;EAGA,OAlDO;;;AAoDX;EACI;EAGA,OAxDO;;;AA2DX;EACI;EAGA,OAzDE;;;AA+DN;EACI;;;AAGJ;EACI;EACA,OAxEK;EAyEL;;;AAGJ;EACI,WApEO;EAqEP,OAvEK;EAwEL;;;AAEJ;EACI;EACA,OA5EK;EA6EL;;;AAEJ;EACI;EACA,OAjFK;EAkFL;;;AAEJ;EACI;EACA,OAtFK;EAuFL;;;AAGJ;EACI,OAhGK;EAiGL,kBAvGO;EAwGP,WA3FO;;;AA6FX;EACI,OArGK;EAsGL;EACA,WAhGO;;;AAmGX;EACI,OA7GK;EA8GL,kBAlHO;;;AAoHX;EACI,OAjHK;EAkHL;EACA,WA1GO;;;AA6GX;EACI,OAnHK;EAoHL,kBA5HO;;;AA8HX;EACI,OAvHK;EAwHL;EACA,WApHO;;;AAuHX;EACI,OAnIG;EAoIH,kBAtIO;;;AAwIX;EACI,OAvIG;EAwIH;EACA,WA9HO;;;AAiIX;EACI,OA5II;EA6IJ,kBAhJO;EAiJP,WApIO;;;AAsIX;EACI,OAjJI;EAkJJ;EACA,WAzIO;;;AA4IX;EACI,OArJG;EAsJH,kBA3JO;EA4JP,WA/IO;;;AAiJX;EACI,OA1JG;EA2JH;EACA,WApJO;;;AAuJX;EACI,OArKO;EAsKP,WAzJO;;;AA2JX;EACI,OAxKO;EAyKP,WA7JO;;;AA+JX;EACI,OA3KG;EA4KH,WAjKO;;;AAmKX;EACI,OAzKK;EA0KL,WArKO;;;AAuKX;EACI,OAlLI;EAmLJ,WAzKO;;;AA6KX;EACI;IACI;;EAEJ;IACI;;;AAGR;EACI;IACI;;EAEJ;IACI;;;AAIR;EACI;IACI,OArMC;;EAuML;IACI,OA7MA;;;AAgNR;EACI;IACI,OAlNA;;EAoNJ;IACI,OAhNC;;;AAoNT;EACI,kBA/NC;EAgOD;;;AAEJ;EACI;EACA;EACA;EACA;EACA,OA1NK;;;AA4NT;EACI;EACA;;;AAEJ;EAgBI;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAxBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAEJ;EACI,OAlPF;;;AAgQN;EACI,OAjQE;EAkQF,kBAzQO;EA0QP;EACH;EACA;;;AAED;EAaI,OAnRK;EAoRL,kBA5RO;EA6RP;EACA;EACH;EACG;;AAjBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;;AAWR;EACI;EACA;EACH;EACG,kBAxSO;EAySP;EACH;EACA;;;AAGD;EAEC;EACG;EACH;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACG;EACH;EACG;EACH,kBA1TQ","file":"style.css"}
{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAmBA;EACI;EACA;;;AASJ;EACI,kBA7BC;EA8BD;;;AAGJ;EACI;EACA;;;AAGJ;EACI,WAxBO;;;AA2BX;EACI;EAEA;EAEA,OA5CO;;;AA8CX;EACI;EAGA,OAlDO;;;AAoDX;EACI;EAGA,OAxDO;;;AA2DX;EACI;EAGA,OAzDE;;;AA+DN;EACI;;;AAGJ;EACI;EACA,OAxEK;EAyEL;;;AAGJ;EACI,WApEO;EAqEP,OAvEK;EAwEL;;;AAEJ;EACI;EACA,OA5EK;EA6EL;;;AAEJ;EACI;EACA,OAjFK;EAkFL;;;AAEJ;EACI;EACA,OAtFK;EAuFL;;;AAGJ;EACI,OAhGK;EAiGL,kBAvGO;EAwGP,WA3FO;;;AA6FX;EACI,OArGK;EAsGL;EACA,WAhGO;;;AAmGX;EACI,OA7GK;EA8GL,kBAlHO;;;AAoHX;EACI,OAjHK;EAkHL;EACA,WA1GO;;;AA6GX;EACI,OAnHK;EAoHL,kBA5HO;;;AA8HX;EACI,OAvHK;EAwHL;EACA,WApHO;;;AAuHX;EACI,OAnIG;EAoIH,kBAtIO;;;AAwIX;EACI,OAvIG;EAwIH;EACA,WA9HO;;;AAiIX;EACI,OA5II;EA6IJ,kBAhJO;EAiJP,WApIO;;;AAsIX;EACI,OAjJI;EAkJJ;EACA,WAzIO;;;AA4IX;EACI,OArJG;EAsJH,kBA3JO;EA4JP,WA/IO;;;AAiJX;EACI,OA1JG;EA2JH;EACA,WApJO;;;AAuJX;EACI,OAlKI;EAmKJ;EACA,WA1JO;;;AA8JX;EACI,OA5KO;;;AA+KX;EACI,OA7KI;;;AAgLR;EACI,OA5KK;;;AA+KT;EACI,OApLK;;;AAuLT;EACI,OAtLK;;;AAyLT;EACI,OAzLE;;;AA6LN;EACI,OArMO;;;AAwMX;EACI,OAtMI;;;AAyMR;EACI,OArMK;;;AAwMT;EACI,OA7MK;;;AAgNT;EACI,OA/MK;;;AAkNT;EACI,OAlNE;;;AAqNN;EACI,OA7NO;EA8NP,WAjNO;;;AAmNX;EACI,OAhOO;EAiOP,WArNO;;;AAuNX;EACI,OAnOG;EAoOH,WAzNO;;;AA2NX;EACI,OAjOK;EAkOL,WA7NO;;;AA+NX;EACI,OA1OI;EA2OJ,WAjOO;;;AAqOX;EACI;IACI;;EAEJ;IACI;;;AAGR;EACI;IACI;;EAEJ;IACI;;;AAIR;EACI;IACI,OA7PC;;EA+PL;IACI,OArQA;;;AAwQR;EACI;IACI,OA1QA;;EA4QJ;IACI,OAxQC;;;AA4QT;EACI,kBAvRC;EAwRD;;;AAEJ;EACI;EACA;EACA;EACA;EACA,OAlRK;;;AAoRT;EACI;EACA;;;AAEJ;EAgBI;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAxBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;AAEJ;EACI,OA1SF;;;AAwTN;EACI,OAzTE;EA0TF,kBAjUO;EAkUP;EACH;EACA;;;AAED;EAaI,OA3UK;EA4UL,kBApVO;EAqVP;EACA;EACH;EACG;;AAjBA;EACI;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;;;AAWR;EACI;EACA;EACH;EACG,kBAhWO;EAiWP;EACH;EACA;;;AAGD;EAEC;EACG;EACH;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACG;EACH;EACG;EACH,kBAlXQ","file":"style.css"}

View file

@ -166,6 +166,62 @@ $textsize: 16px;
font-size: $textsize;
}
.network-data-text {
color: $green;
margin-right: 6px;
font-size: $textsize;
}
// <= 0% (Below MinUploadBytes)
.network-up-under {
color: $inactive;
}
// <= 25%
.network-up-low {
color: $green;
}
// <= 50%
.network-up-mid-low {
color: $yellow;
}
// <= 75%
.network-up-mid-high {
color: $orange;
}
// <= 100%
.network-up-high {
color: $purple;
}
// > 100% (Above MaxUploadBytes)
.network-up-over {
color: $red;
}
// <= 0% (Below MinDownloadBytes)
.network-down-under {
color: $inactive;
}
// <= 25%
.network-down-low {
color: $green;
}
// <= 50%
.network-down-mid-low {
color: $yellow;
}
// <= 75%
.network-down-mid-high {
color: $orange;
}
// <= 100%
.network-down-high {
color: $purple;
}
// > 100% (Above MaxDownloadBytes)
.network-down-over {
color: $red;
}
.ws-dead {
color: $inactive;
font-size: $textsize;

View file

@ -1,4 +1,4 @@
# Example configuration.
# Example configuration.
# Everything after '#' is ignored
# Format of the variables:
# [variable]: [value]
@ -34,3 +34,21 @@ CenterTime: true
# Sets the audio slider to be on reveal (Just like the sensors) when true. Only affects the bar.
AudioRevealer: false
# The network adapter to use. You can query /sys/class/net for all possible values
NetworkAdapter: eno1
# Disables the network widget when set to false
NetworkWidget: true
# These set the range for the network widget. The widget changes colors at six intervals:
# - Below Min...Bytes ("under")
# - Between ]0%;25%]. 0% = Min...Bytes; 100% = Max...Bytes ("low")
# - Between ]25%;50%]. 0% = Min...Bytes; 100% = Max...Bytes ("mid-low")
# - Between ]50%;75%]. 0% = Min...Bytes; 100% = Max...Bytes ("mid-high")
# - Between ]75%;100%]. 0% = Min...Bytes; 100% = Max...Bytes ("high")
# - Above Max...Bytes ("over")
MinDownloadBytes: 0
MaxDownloadBytes: 10485760 # 10 * 1024 * 1024 = 10 MiB
MinUploadBytes: 0
MaxUploadBytes: 5242880 # 5 * 1024 * 1024 = 5 MiB

View file

@ -155,6 +155,23 @@ namespace Bar
return TimerResult::Ok;
}
Text* networkText;
TimerResult UpdateNetwork(NetworkSensor& sensor)
{
double bpsUp = System::GetNetworkBpsUpload(updateTime / 1000.0);
double bpsDown = System::GetNetworkBpsDownload(updateTime / 1000.0);
std::string upload = Utils::StorageUnitDynamic(bpsUp, "%0.1f%s");
std::string download = Utils::StorageUnitDynamic(bpsDown, "%0.1f%s");
networkText->SetText(Config::Get().networkAdapter + ": " + upload + " Up/" + download + " Down");
sensor.SetUp(bpsUp);
sensor.SetDown(bpsDown);
return TimerResult::Ok;
}
TimerResult UpdateTime(Text& text)
{
text.SetText(System::GetTime());
@ -311,6 +328,44 @@ namespace Bar
}
#endif
void WidgetNetwork(Widget& parent)
{
auto eventBox = Widget::Create<EventBox>();
{
auto box = Widget::Create<Box>();
box->SetSpacing({0, false});
box->SetHorizontalTransform({-1, true, Alignment::Right});
{
auto revealer = Widget::Create<Revealer>();
revealer->SetTransition({TransitionType::SlideLeft, 500});
// Add event to eventbox for the revealer to open
eventBox->SetEventFn(
[textRevealer = revealer.get()](EventBox&, bool hovered)
{
textRevealer->SetRevealed(hovered);
});
{
auto text = Widget::Create<Text>();
text->SetClass("network-data-text");
DynCtx::networkText = text.get();
revealer->AddChild(std::move(text));
}
auto sensor = Widget::Create<NetworkSensor>();
sensor->SetLimitUp({(double)Config::Get().minUploadBytes, (double)Config::Get().maxUploadBytes});
sensor->SetLimitDown({(double)Config::Get().minDownloadBytes, (double)Config::Get().maxDownloadBytes});
sensor->AddTimer<NetworkSensor>(DynCtx::UpdateNetwork, DynCtx::updateTime);
sensor->SetHorizontalTransform({24, true, Alignment::Fill});
box->AddChild(std::move(revealer));
box->AddChild(std::move(sensor));
}
eventBox->AddChild(std::move(box));
}
parent.AddChild(std::move(eventBox));
}
void WidgetSensors(Widget& parent)
{
WidgetSensor(parent, DynCtx::UpdateDisk, "disk-util-progress", "disk-data-text", DynCtx::diskText);
@ -476,6 +531,8 @@ namespace Bar
if (RuntimeConfig::Get().hasBlueZ)
WidgetBluetooth(*right);
#endif
if (Config::Get().networkWidget)
WidgetNetwork(*right);
WidgetSensors(*right);

View file

@ -60,6 +60,33 @@ namespace Utils
snprintf(buf, sizeof(buf), fmt, x);
return buf;
}
// Format must be something like %0.1f %s
inline std::string StorageUnitDynamic(double bytes, const char* fmt)
{
constexpr double KiB = 1024;
constexpr double MiB = 1024 * KiB;
constexpr double GiB = 1024 * MiB;
char buf[128];
if (bytes >= GiB)
{
snprintf(buf, sizeof(buf), fmt, bytes * (1 / GiB), "GiB");
return buf;
}
if (bytes >= MiB)
{
snprintf(buf, sizeof(buf), fmt, bytes * (1 / MiB), "MiB");
return buf;
}
if (bytes >= KiB)
{
snprintf(buf, sizeof(buf), fmt, bytes * (1 / KiB), "KiB");
return buf;
}
snprintf(buf, sizeof(buf), fmt, bytes, "B");
return buf;
}
}
struct Process

View file

@ -119,6 +119,7 @@ void Config::Load()
bool foundProperty = false;
AddConfigVar("CPUThermalZone", config.cpuThermalZone, lineView, foundProperty);
AddConfigVar("NetworkAdapter", config.networkAdapter, lineView, foundProperty);
AddConfigVar("SuspendCommand", config.suspendCommand, lineView, foundProperty);
AddConfigVar("LockCommand", config.lockCommand, lineView, foundProperty);
AddConfigVar("ExitCommand", config.exitCommand, lineView, foundProperty);
@ -132,6 +133,12 @@ void Config::Load()
AddConfigVar("CenterTime", config.centerTime, lineView, foundProperty);
AddConfigVar("AudioRevealer", config.audioRevealer, lineView, foundProperty);
AddConfigVar("NetworkWidget", config.networkWidget, lineView, foundProperty);
AddConfigVar("MinUploadBytes", config.minUploadBytes, lineView, foundProperty);
AddConfigVar("MaxUploadBytes", config.maxUploadBytes, lineView, foundProperty);
AddConfigVar("MinDownloadBytes", config.minDownloadBytes, lineView, foundProperty);
AddConfigVar("MaxDownloadBytes", config.maxDownloadBytes, lineView, foundProperty);
if (foundProperty == false)
{

View file

@ -2,19 +2,27 @@
#include <string>
#include <vector>
class Config
class Config
{
public:
std::string cpuThermalZone = ""; // idk, no standard way of doing this.
std::string cpuThermalZone = ""; // idk, no standard way of doing this.
std::string networkAdapter = "eno1"; // Is this standard?
std::string suspendCommand = "systemctl suspend";
std::string lockCommand = ""; // idk, no standard way of doing this.
std::string exitCommand = ""; // idk, no standard way of doing this.
std::string lockCommand = ""; // idk, no standard way of doing this.
std::string exitCommand = ""; // idk, no standard way of doing this.
std::string batteryFolder = ""; // this can be BAT0, BAT1, etc. Usually in /sys/class/power_supply
std::vector<std::string> workspaceSymbols = std::vector<std::string>(9, "");
std::string defaultWorkspaceSymbol = "";
bool centerTime = true;
bool audioRevealer = false;
bool networkWidget = true;
// Controls for color progression of the network widget
uint32_t minUploadBytes = 0; // Bottom limit of the network widgets upload. Everything below it is considered "under"
uint32_t maxUploadBytes = 4 * 1024 * 1024; // 4 MiB Top limit of the network widgets upload. Everything above it is considered "over"
uint32_t minDownloadBytes = 0; // Bottom limit of the network widgets download. Everything above it is considered "under"
uint32_t maxDownloadBytes = 10 * 1024 * 1024; // 10 MiB Top limit of the network widgets download. Everything above it is considered "over"
static void Load();
static const Config& Get();

View file

@ -463,6 +463,47 @@ namespace System
}
#endif
double GetNetworkBpsCommon(double dt, uint64_t& prevBytes, const std::string& deviceFile)
{
std::ifstream bytes(deviceFile);
ASSERT(bytes.is_open(), "Couldn't open " << deviceFile);
std::string bytesStr;
std::getline(bytes, bytesStr);
uint64_t curBytes = std::stoull(bytesStr);
if (prevBytes == UINT64_MAX)
{
prevBytes = curBytes;
return 0;
}
else
{
uint64_t diffBytes = curBytes - prevBytes;
prevBytes = curBytes;
// Is double precision a problem here?
return diffBytes / dt;
}
}
double GetNetworkBpsUpload(double dt)
{
// Better safe than sorry. Isn't 32bit max only a few GB?
static uint64_t prevUploadBytes = UINT64_MAX;
// Apparently /sys/class/net/.../statistics/[t/r]x_bytes is valid for all net devices under Linux
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net-statistics
return GetNetworkBpsCommon(dt, prevUploadBytes, "/sys/class/net/" + Config::Get().networkAdapter + "/statistics/tx_bytes");
}
double GetNetworkBpsDownload(double dt)
{
// Better safe than sorry. Isn't 32bit max only a few GB?
static uint64_t prevDownloadBytes = UINT64_MAX;
// Apparently /sys/class/net/.../statistics/[t/r]x_bytes is valid for all net devices under Linux
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net-statistics
return GetNetworkBpsCommon(dt, prevDownloadBytes, "/sys/class/net/" + Config::Get().networkAdapter + "/statistics/rx_bytes");
}
std::string GetTime()
{
time_t stdTime = time(NULL);

View file

@ -93,6 +93,11 @@ namespace System
std::string GetWorkspaceSymbol(int index);
#endif
// Bytes per second upload. dx is time since last call. Will always return 0 on first run
double GetNetworkBpsUpload(double dt);
// Bytes per second download. dx is time since last call. Will always return 0 on first run
double GetNetworkBpsDownload(double dt);
std::string GetTime();
void Shutdown();

View file

@ -1,5 +1,6 @@
#include "Widget.h"
#include "Common.h"
#include "CSS.h"
#include <cmath>
@ -298,6 +299,146 @@ void Sensor::Draw(cairo_t* cr)
gdk_rgba_free(fgCol);
}
static std::string NetworkSensorPercentToCSS(double percent)
{
if (percent <= 0.)
{
return "under";
}
else if (percent <= 0.25)
{
return "low";
}
else if (percent <= 0.50)
{
return "mid-low";
}
else if (percent <= 0.75)
{
return "mid-high";
}
else if (percent <= 1.)
{
return "high";
}
else
{
return "over";
}
}
static double NetworkSensorRateToPercent(double rate, Range range)
{
return (rate - range.min) / (range.max - range.min);
}
void NetworkSensor::Create()
{
CairoArea::Create();
// Add virtual children for style context(I know, it is really gross)
contextUp = Widget::Create<Box>();
contextUp->SetSpacing({0, true});
contextUp->SetClass("network-up-under");
contextUp->SetHorizontalTransform({0, false, Alignment::Fill});
contextUp->Create();
contextDown = Widget::Create<Box>();
contextDown->SetSpacing({0, true});
contextDown->SetClass("network-down-under");
contextDown->SetHorizontalTransform({0, false, Alignment::Fill});
contextDown->Create();
}
void NetworkSensor::SetUp(double val)
{
if (!contextUp)
{
return;
}
up = NetworkSensorRateToPercent(val, limitUp);
// Add css class
std::string newClass = NetworkSensorPercentToCSS(up);
contextUp->SetClass("network-up-" + newClass);
// Schedule redraw
if (m_Widget)
{
gtk_widget_queue_draw(m_Widget);
}
}
void NetworkSensor::SetDown(double val)
{
if (!contextDown)
{
return;
}
down = NetworkSensorRateToPercent(val, limitDown);
// Add css class
std::string newClass = NetworkSensorPercentToCSS(down);
contextDown->SetClass("network-down-" + newClass);
// Schedule redraw
if (m_Widget)
{
gtk_widget_queue_draw(m_Widget);
}
}
void NetworkSensor::Draw(cairo_t* cr)
{
constexpr double epsilon = 1;
Quad q = GetQuad();
auto virtToPx = [&](double virtPx)
{
return q.size * (virtPx / 24.f);
};
GdkRGBA* colUp;
GdkRGBA* colDown;
gtk_style_context_get(gtk_widget_get_style_context(contextUp->Get()), GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_COLOR, &colUp, NULL);
gtk_style_context_get(gtk_widget_get_style_context(contextDown->Get()), GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_COLOR, &colDown, NULL);
// Upload
cairo_set_source_rgb(cr, colUp->red, colUp->green, colUp->blue);
// Triangle
cairo_move_to(cr, q.x + virtToPx(6), q.y + virtToPx(0)); // Top mid
cairo_line_to(cr, q.x + virtToPx(0), q.y + virtToPx(10)); // Left bottom
cairo_line_to(cr, q.x + virtToPx(12), q.y + virtToPx(10)); // Right bottom
cairo_close_path(cr);
cairo_fill(cr);
// Rectangle
// Go a bit above, to avoid gaps between tri and quad
cairo_rectangle(cr, q.x + virtToPx(4), q.y + virtToPx(10 - epsilon), virtToPx(4), virtToPx(12 + epsilon));
cairo_fill(cr);
// Download
cairo_set_source_rgb(cr, colDown->red, colDown->green, colDown->blue);
// Triangle
cairo_move_to(cr, q.x + virtToPx(18), q.y + virtToPx(24)); // Bottom mid
cairo_line_to(cr, q.x + virtToPx(12), q.y + virtToPx(14)); // Left top
cairo_line_to(cr, q.x + virtToPx(24), q.y + virtToPx(14)); // Right top
cairo_close_path(cr);
cairo_fill(cr);
// Rectangle
// Go a bit below, to avoid gaps between tri and quad
cairo_rectangle(cr, q.x + virtToPx(16), q.y + virtToPx(2), virtToPx(4), virtToPx(12 + epsilon));
cairo_fill(cr);
gdk_rgba_free(colUp);
gdk_rgba_free(colDown);
}
void Revealer::SetTransition(Transition transition)
{
m_Transition = transition;

View file

@ -226,6 +226,32 @@ private:
SensorStyle m_Style{};
};
class NetworkSensor : public CairoArea
{
public:
virtual void Create() override;
void SetLimitUp(Range limit) { limitUp = limit; };
void SetLimitDown(Range limit) { limitDown = limit; };
void SetUp(double val);
void SetDown(double val);
private:
void Draw(cairo_t* cr) override;
// These are in percent
double up, down;
Range limitUp;
Range limitDown;
// What I do here is a little bit gross, but I need a working style context
// Just manually creating a style context doesn't work for me.
std::unique_ptr<Box> contextUp;
std::unique_ptr<Box> contextDown;
};
class Revealer : public Widget
{
public: