// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md #include #include "DrawCommand.hpp" #include "ProgressBar.hpp" namespace gui { ProgressBar::ProgressBar(Item *parent, std::uint32_t x, std::uint32_t y, std::uint32_t w, std::uint32_t h) : Rect{parent, x, y, w, h} { setFillColor(ColorFullBlack); setPenWidth(2); createWidgets(); updateDrawArea(); } void ProgressBar::createWidgets() { // fillRect is smaller, to avoid border overlapping fillRect = new gui::Rect(this, 0, 1, widgetArea.w, widgetArea.h - 2); fillRect->setRadius(widgetArea.h / 2 - 1); fillRect->setFilled(true); fillRect->setFillColor(Color{0, 0}); Rect::setRadius(widgetArea.h / 2); } void ProgressBar::setMaximum(unsigned int value) noexcept { maxValue = value; if (currentValue > maxValue) { currentValue = maxValue; } } bool ProgressBar::setValue(unsigned int value) noexcept { currentValue = std::clamp(value, 0U, maxValue); return currentValue == value; } void ProgressBar::setPercentageValue(unsigned int value) noexcept { const auto percent = static_cast(value) / 100.0f; const auto absoluteValue = std::lround(static_cast(maxValue) * percent); setValue(absoluteValue); } int ProgressBar::getMaximum() const noexcept { return maxValue; } void ProgressBar::buildDrawListImplementation(std::list &commands) { uint32_t progressSize = maxValue == 0U ? 0 : (currentValue * widgetArea.w) / maxValue; drawArea.w = progressSize; gui::Rect::buildDrawListImplementation(commands); } bool ProgressBar::onDimensionChanged([[maybe_unused]] const BoundingBox &oldDim, const BoundingBox &newDim) { fillRect->setSize(newDim.w, newDim.h); return true; } CircularProgressBar::CircularProgressBar(Item *parent, const Circle::ShapeParams &shape) : Circle{parent, shape} { createWidgets(); updateDrawArea(); } void CircularProgressBar::createWidgets() { const auto progressArcRadius = radius + 1; const auto progressArcWidth = penWidth + 2; Arc::ShapeParams arcParams; arcParams.setCenterPoint(center) .setRadius(progressArcRadius) .setStartAngle(-90) // Start drawing the circle from top. .setSweepAngle(0) .setPenWidth(progressArcWidth) .setBorderColor(ColorFullBlack); progressArc = new Arc(this, arcParams); Circle::ShapeParams indicatorParams; indicatorParams.setCenterPoint(calculateProgressIndicatorCenter()) .setRadius(progressArcWidth) .setPenWidth(penWidth + 1) .setBorderColor(ColorFullBlack) .setFillColor(ColorFullBlack); progressIndicator = new Circle(this, indicatorParams); } Point CircularProgressBar::calculateProgressIndicatorCenter() const { using namespace trigonometry; const auto sweepAngleRadians = toRadians(progressArc->getSweepAngle() + progressArc->getStartAngle()); return Point(center.x + AdjacentSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)), center.y + OppositeSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2))); } void CircularProgressBar::setMaximum(unsigned int value) noexcept { maxValue = value; if (currentValue > maxValue) { currentValue = maxValue; } } bool CircularProgressBar::setValue(unsigned int value) noexcept { currentValue = std::clamp(value, 0U, maxValue); return value == currentValue; } void CircularProgressBar::setPercentageValue(unsigned int value) noexcept { const auto percent = static_cast(value) / 100.0f; const auto absoluteValue = std::lround(static_cast(maxValue) * percent); setValue(absoluteValue); } int CircularProgressBar::getMaximum() const noexcept { return maxValue; } float CircularProgressBar::getPercentageValue() const { if (maxValue == 0) { return .0f; } return static_cast(currentValue) / maxValue; } void CircularProgressBar::buildDrawListImplementation(std::list &commands) { using namespace trigonometry; progressArc->setSweepAngle(std::ceil(getPercentageValue() * FullAngle)); progressIndicator->setCenter(calculateProgressIndicatorCenter()); Circle::buildDrawListImplementation(commands); } bool CircularProgressBar::onDimensionChanged([[maybe_unused]] const BoundingBox &oldDim, [[maybe_unused]] const BoundingBox &newDim) { return true; } ArcProgressBar::ArcProgressBar(Item *parent, const Arc::ShapeParams &shape, ProgressDirection direction, ProgressChange change) : Arc{parent, shape}, direction{direction}, change{change} { if (direction == ProgressDirection::CounterClockwise) { start -= sweep; } createWidgets(); updateDrawArea(); } void ArcProgressBar::createWidgets() { // Arc progress indicator (stronger line) must be a bit wider than the base circle // Those values were selected to match the design and look good enough on multiple // radius and penWidth values const auto progressArcRadius = radius + 3; const auto progressArcWidth = penWidth + 7; const auto progressIndicatorRadius = (progressArcWidth - 1) / 2; Arc::ShapeParams arcParams; arcParams.setCenterPoint(center) .setRadius(progressArcRadius) .setSweepAngle(0) .setPenWidth(progressArcWidth) .setBorderColor(ColorFullBlack); progressArc = new Arc(this, arcParams); Circle::ShapeParams indicatorStartParams; indicatorStartParams.setCenterPoint(calculateStartIndicatorCenter()) .setRadius(progressIndicatorRadius) .setPenWidth(2) .setBorderColor(ColorFullBlack) .setFillColor(ColorFullBlack); progressStartIndicator = new Circle(this, indicatorStartParams); Circle::ShapeParams indicatorEndParams; indicatorEndParams.setCenterPoint(calculateEndIndicatorCenter()) .setRadius(progressIndicatorRadius) .setPenWidth(2) .setBorderColor(ColorFullBlack) .setFillColor(ColorFullBlack); progressEndIndicator = new Circle(this, indicatorEndParams); } Point ArcProgressBar::calculateStartIndicatorCenter() const { using namespace trigonometry; const auto sweepAngleRadians = toRadians(progressArc->getSweepAngle() + progressArc->getStartAngle()); return Point(center.x + AdjacentSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)), center.y + OppositeSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2))); } Point ArcProgressBar::calculateEndIndicatorCenter() const { using namespace trigonometry; const auto sweepAngleRadians = toRadians(progressArc->getStartAngle()); return Point(center.x + AdjacentSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2)), center.y + OppositeSide::fromAngle(sweepAngleRadians, radius - (penWidth / 2))); } void ArcProgressBar::setMaximum(unsigned int value) noexcept { maxValue = value; if (currentValue > maxValue) { currentValue = maxValue; } } bool ArcProgressBar::setValue(unsigned int value) noexcept { switch (change) { case ProgressChange::IncrementFromZero: currentValue = std::clamp(value, 0U, maxValue); break; case ProgressChange::DecrementFromFull: currentValue = maxValue - std::clamp(value, 0U, maxValue); break; default: break; } return value == currentValue; } void ArcProgressBar::setPercentageValue(unsigned int value) noexcept { const auto percent = static_cast(value) / 100.0f; const auto absoluteValue = std::lround(static_cast(maxValue) * percent); setValue(absoluteValue); } int ArcProgressBar::getMaximum() const noexcept { return maxValue; } float ArcProgressBar::getPercentageValue() const { if (maxValue == 0) { return .0f; } return static_cast(currentValue) / maxValue; } void ArcProgressBar::buildDrawListImplementation(std::list &commands) { const auto dTheta = std::ceil(getPercentageValue() * sweep); progressArc->setSweepAngle(dTheta); if ((direction == ProgressDirection::Clockwise) != (change == ProgressChange::IncrementFromZero)) { progressArc->setStartAngle(start + sweep - dTheta); } else { progressArc->setStartAngle(start); } progressStartIndicator->setCenter(calculateStartIndicatorCenter()); progressEndIndicator->setCenter(calculateEndIndicatorCenter()); const auto progressItemsVisible = ((dTheta != 0) || (change == ProgressChange::IncrementFromZero)); progressArc->setVisible(progressItemsVisible); progressStartIndicator->setVisible(progressItemsVisible); progressEndIndicator->setVisible(progressItemsVisible); Arc::buildDrawListImplementation(commands); } bool ArcProgressBar::onDimensionChanged([[maybe_unused]] const BoundingBox &oldDim, [[maybe_unused]] const BoundingBox &newDim) { return true; } } /* namespace gui */