ExecutionProgress.cpp (29489B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "ExecutionProgress.h" 5 #include "VTSupport.h" 6 #include "AppInstallerRuntime.h" 7 #include "Sixel.h" 8 9 using namespace AppInstaller::Settings; 10 using namespace AppInstaller::CLI::VirtualTerminal; 11 using namespace std::string_view_literals; 12 13 namespace AppInstaller::CLI::Execution 14 { 15 namespace 16 { 17 static constexpr size_t s_ProgressBarCellWidth = 30; 18 19 struct BytesFormatData 20 { 21 uint64_t PowerOfTwo; 22 std::string_view Name; 23 }; 24 25 BytesFormatData s_bytesFormatData[] = 26 { 27 // Multi-terabyte installers should be fairly rare for the foreseeable future... 28 { 40, "TB"sv }, 29 { 30, "GB"sv }, 30 { 20, "MB"sv }, 31 { 10, "KB"sv }, 32 { 0, "B"sv }, 33 }; 34 35 const BytesFormatData& GetFormatForSize(uint64_t bytes) 36 { 37 for (const auto& format : s_bytesFormatData) 38 { 39 if (bytes > (1ull << format.PowerOfTwo)) 40 { 41 return format; 42 } 43 } 44 45 // Just to make the compiler happy, return the last in the list if we get here. 46 return s_bytesFormatData[ARRAYSIZE(s_bytesFormatData) - 1]; 47 } 48 49 void OutputBytes(BaseStream& out, uint64_t byteCount) 50 { 51 const BytesFormatData& bfd = GetFormatForSize(byteCount); 52 53 uint64_t integralAmount = byteCount >> bfd.PowerOfTwo; 54 uint64_t remainder = byteCount & ((1ull << bfd.PowerOfTwo) - 1); 55 size_t remainderDigits = 0; 56 57 if (integralAmount < 10) 58 { 59 remainder *= 100; 60 remainderDigits = 2; 61 } 62 else if (integralAmount < 100) 63 { 64 remainder *= 10; 65 remainderDigits = 1; 66 } 67 else if (integralAmount < 1000) 68 { 69 // Put an extra space to ensure a consistent 4 chars per numeric output 70 out << ' '; 71 } 72 73 out << integralAmount; 74 75 if (remainderDigits) 76 { 77 remainder = remainder >> bfd.PowerOfTwo; 78 out << '.' << std::setw(remainderDigits) << std::setfill('0') << remainder; 79 } 80 81 out << ' ' << bfd.Name; 82 } 83 84 void SetColor(BaseStream& out, const TextFormat::Color& color, bool foregroundOnly) 85 { 86 out << TextFormat::Foreground::Extended(color); 87 88 if (!foregroundOnly) 89 { 90 constexpr uint8_t divisor = 3; 91 92 auto reduced = color; 93 reduced.R /= divisor; 94 reduced.G /= divisor; 95 reduced.B /= divisor; 96 97 out << TextFormat::Background::Extended(reduced); 98 } 99 } 100 101 void SetRainbowColor(BaseStream& out, size_t i, size_t max, bool foregroundOnly) 102 { 103 TextFormat::Color rainbow[] = 104 { 105 { 0xff, 0x00, 0x00 }, 106 { 0xff, 0x77, 0x00 }, 107 { 0xff, 0xdd, 0x00 }, 108 { 0x00, 0xff, 0x00 }, 109 { 0x00, 0x00, 0xff }, 110 { 0x8a, 0x2b, 0xe2 }, 111 { 0xc7, 0x7d, 0xf3 }, 112 }; 113 114 double target = (static_cast<double>(i) / (max - 1)) * (ARRAYSIZE(rainbow) - 1); 115 size_t lower = static_cast<size_t>(std::floor(target)); 116 const auto& lowerVal = rainbow[lower]; 117 TextFormat::Color result; 118 119 if (lower == (ARRAYSIZE(rainbow) - 1)) 120 { 121 result = lowerVal; 122 } 123 else 124 { 125 double upperContribution = target - lower; 126 127 #define AICLI_AVERAGE(v) static_cast<uint8_t>(((lowerVal.v * (1.0 - upperContribution)) + (rainbow[lower + 1].v * upperContribution))) 128 result = { AICLI_AVERAGE(R), AICLI_AVERAGE(G), AICLI_AVERAGE(B) }; 129 } 130 131 SetColor(out, result, foregroundOnly); 132 } 133 } 134 135 // Shared functionality for progress visualizers. 136 struct ProgressVisualizerBase 137 { 138 ProgressVisualizerBase(BaseStream& stream, bool enableVT) : 139 m_out(stream), m_enableVT(enableVT) {} 140 141 void SetMessage(std::string_view message) 142 { 143 std::atomic_store(&m_message, std::make_shared<Utility::NormalizedString>(message)); 144 } 145 146 std::shared_ptr<Utility::NormalizedString> Message() 147 { 148 return std::atomic_load(&m_message); 149 } 150 151 protected: 152 BaseStream& m_out; 153 154 bool VT_Enabled() const { return m_enableVT; } 155 156 void ClearLine() 157 { 158 if (VT_Enabled()) 159 { 160 m_out << TextModification::EraseLineEntirely << '\r'; 161 } 162 else 163 { 164 m_out << '\r' << std::string(GetConsoleWidth(), ' ') << '\r'; 165 } 166 } 167 168 private: 169 bool m_enableVT = false; 170 std::shared_ptr<Utility::NormalizedString> m_message; 171 }; 172 173 // Shared functionality for progress visualizers. 174 struct CharacterProgressVisualizerBase : public ProgressVisualizerBase 175 { 176 CharacterProgressVisualizerBase(BaseStream& stream, bool enableVT, VisualStyle style) : 177 ProgressVisualizerBase(stream, enableVT && style != AppInstaller::Settings::VisualStyle::NoVT), m_style(style) {} 178 179 protected: 180 Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent; 181 182 // Applies the selected visual style. 183 void ApplyStyle(size_t i, size_t max, bool foregroundOnly) 184 { 185 if (!VT_Enabled()) 186 { 187 // Either no style set or VT disabled 188 return; 189 } 190 switch (m_style) 191 { 192 case VisualStyle::Retro: 193 m_out << TextFormat::Default; 194 break; 195 case VisualStyle::Accent: 196 SetColor(m_out, TextFormat::Color::GetAccentColor(), foregroundOnly); 197 break; 198 case VisualStyle::Rainbow: 199 SetRainbowColor(m_out, i, max, foregroundOnly); 200 break; 201 default: 202 LOG_HR(E_UNEXPECTED); 203 } 204 } 205 }; 206 207 // Displays an indefinite spinner via a character. 208 struct CharacterIndefiniteSpinner : public CharacterProgressVisualizerBase, public IIndefiniteSpinner 209 { 210 CharacterIndefiniteSpinner(BaseStream& stream, bool enableVT, VisualStyle style) : 211 CharacterProgressVisualizerBase(stream, enableVT, style) {} 212 213 void ShowSpinner() override 214 { 215 if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) 216 { 217 m_spinnerRunning = true; 218 m_spinnerJob = std::async(std::launch::async, &CharacterIndefiniteSpinner::ShowSpinnerInternal, this); 219 } 220 } 221 222 void StopSpinner() override 223 { 224 if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) 225 { 226 m_canceled = true; 227 m_spinnerJob.get(); 228 } 229 } 230 231 void SetMessage(std::string_view message) override 232 { 233 ProgressVisualizerBase::SetMessage(message); 234 } 235 236 std::shared_ptr<Utility::NormalizedString> Message() override 237 { 238 return ProgressVisualizerBase::Message(); 239 } 240 241 private: 242 std::atomic<bool> m_canceled = false; 243 std::atomic<bool> m_spinnerRunning = false; 244 std::future<void> m_spinnerJob; 245 246 void ShowSpinnerInternal() 247 { 248 char spinnerChars[] = { '-', '\\', '|', '/' }; 249 250 // First wait for a small amount of time to enable a fast task to skip 251 // showing anything, or a progress task to skip straight to progress. 252 Sleep(100); 253 254 if (!m_canceled) 255 { 256 if (VT_Enabled()) 257 { 258 // Additional VT-based progress reporting, for terminals that support it 259 m_out << Progress::Construct(Progress::State::Indeterminate); 260 } 261 262 // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. 263 std::string_view indent = " "; 264 std::shared_ptr<Utility::NormalizedString> message = ProgressVisualizerBase::Message(); 265 size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0; 266 267 for (size_t i = 0; !m_canceled; ++i) 268 { 269 constexpr size_t repetitionCount = 20; 270 ApplyStyle(i % repetitionCount, repetitionCount, true); 271 m_out << '\r' << indent << spinnerChars[i % ARRAYSIZE(spinnerChars)]; 272 m_out.RestoreDefault(); 273 274 std::shared_ptr<Utility::NormalizedString> newMessage = ProgressVisualizerBase::Message(); 275 std::string eraser; 276 if (newMessage) 277 { 278 size_t newLength = Utility::UTF8ColumnWidth(*newMessage); 279 280 if (newLength < messageLength) 281 { 282 eraser = std::string(messageLength - newLength, ' '); 283 } 284 285 message = newMessage; 286 messageLength = newLength; 287 } 288 289 m_out << ' ' << (message ? *message : std::string{}) << eraser << std::flush; 290 Sleep(250); 291 } 292 293 ClearLine(); 294 295 if (VT_Enabled()) 296 { 297 m_out << Progress::Construct(Progress::State::None); 298 } 299 } 300 301 m_canceled = false; 302 m_spinnerRunning = false; 303 } 304 }; 305 306 // Displays progress via character output. 307 class CharacterProgressBar : public CharacterProgressVisualizerBase, public IProgressBar 308 { 309 public: 310 CharacterProgressBar(BaseStream& stream, bool enableVT, VisualStyle style) : 311 CharacterProgressVisualizerBase(stream, enableVT, style) {} 312 313 void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override 314 { 315 if (current < m_lastCurrent) 316 { 317 ClearLine(); 318 } 319 320 // TODO: Progress bar does not currently use message 321 if (VT_Enabled()) 322 { 323 ShowProgressWithVT(current, maximum, type); 324 } 325 else 326 { 327 ShowProgressNoVT(current, maximum, type); 328 } 329 330 m_lastCurrent = current; 331 m_isVisible = true; 332 } 333 334 void EndProgress(bool hideProgressWhenDone) override 335 { 336 if (m_isVisible) 337 { 338 if (hideProgressWhenDone) 339 { 340 ClearLine(); 341 } 342 else 343 { 344 m_out << std::endl; 345 } 346 347 if (VT_Enabled()) 348 { 349 // We always clear the VT-based progress bar, even if hideProgressWhenDone is false 350 // since it would be confusing for users if progress continues to be shown after winget exits 351 // (it is typically not automatically cleared by terminals on process exit) 352 m_out << Progress::Construct(Progress::State::None); 353 } 354 355 m_isVisible = false; 356 } 357 } 358 359 private: 360 std::atomic<bool> m_isVisible = false; 361 uint64_t m_lastCurrent = 0; 362 363 void ShowProgressNoVT(uint64_t current, uint64_t maximum, ProgressType type) 364 { 365 m_out << "\r "; 366 367 if (maximum) 368 { 369 const char* const blockOn = u8"\x2588"; 370 const char* const blockOff = u8"\x2592"; 371 constexpr size_t blockWidth = 30; 372 373 double percentage = static_cast<double>(current) / maximum; 374 size_t blocksOn = static_cast<size_t>(std::floor(percentage * blockWidth)); 375 376 for (size_t i = 0; i < blocksOn; ++i) 377 { 378 m_out << blockOn; 379 } 380 381 for (size_t i = 0; i < blockWidth - blocksOn; ++i) 382 { 383 m_out << blockOff; 384 } 385 386 m_out << " "; 387 388 switch (type) 389 { 390 case AppInstaller::ProgressType::Bytes: 391 OutputBytes(m_out, current); 392 m_out << " / "; 393 OutputBytes(m_out, maximum); 394 break; 395 case AppInstaller::ProgressType::Percent: 396 default: 397 m_out << static_cast<int>(percentage * 100) << '%'; 398 break; 399 } 400 } 401 else 402 { 403 switch (type) 404 { 405 case AppInstaller::ProgressType::Bytes: 406 OutputBytes(m_out, current); 407 break; 408 case AppInstaller::ProgressType::Percent: 409 m_out << current << '%'; 410 break; 411 default: 412 m_out << current << " unknowns"; 413 break; 414 } 415 } 416 } 417 418 void ShowProgressWithVT(uint64_t current, uint64_t maximum, ProgressType type) 419 { 420 m_out << TextFormat::Default; 421 422 m_out << "\r "; 423 424 if (maximum) 425 { 426 const char* const blocks[] = 427 { 428 u8" ", // block off 429 u8"\x258F", // block 1/8 430 u8"\x258E", // block 2/8 431 u8"\x258D", // block 3/8 432 u8"\x258C", // block 4/8 433 u8"\x258B", // block 5/8 434 u8"\x258A", // block 6/8 435 u8"\x2589", // block 7/8 436 u8"\x2588" // block on 437 }; 438 const char* const blockOn = blocks[8]; 439 const char* const blockOff = blocks[0]; 440 constexpr size_t blockWidth = s_ProgressBarCellWidth; 441 442 double percentage = static_cast<double>(current) / maximum; 443 size_t blocksOn = static_cast<size_t>(std::floor(percentage * blockWidth)); 444 size_t partialBlockIndex = static_cast<size_t>((percentage * blockWidth - blocksOn) * 8); 445 TextFormat::Color accent = TextFormat::Color::GetAccentColor(); 446 447 for (size_t i = 0; i < blockWidth; ++i) 448 { 449 ApplyStyle(i, blockWidth, false); 450 451 if (i < blocksOn) 452 { 453 m_out << blockOn; 454 } 455 else if (i == blocksOn) 456 { 457 m_out << blocks[partialBlockIndex]; 458 } 459 else 460 { 461 m_out << blockOff; 462 } 463 } 464 465 m_out << TextFormat::Default; 466 467 m_out << " "; 468 469 switch (type) 470 { 471 case AppInstaller::ProgressType::Bytes: 472 OutputBytes(m_out, current); 473 m_out << " / "; 474 OutputBytes(m_out, maximum); 475 break; 476 case AppInstaller::ProgressType::Percent: 477 default: 478 m_out << static_cast<int>(percentage * 100) << '%'; 479 break; 480 } 481 482 // Additional VT-based progress reporting, for terminals that support it 483 m_out << Progress::Construct(Progress::State::Normal, static_cast<int>(percentage * 100)); 484 } 485 else 486 { 487 switch (type) 488 { 489 case AppInstaller::ProgressType::Bytes: 490 OutputBytes(m_out, current); 491 break; 492 case AppInstaller::ProgressType::Percent: 493 m_out << current << '%'; 494 break; 495 default: 496 m_out << current << " unknowns"; 497 break; 498 } 499 } 500 } 501 }; 502 503 // Displays an indefinite spinner via a sixel. 504 struct SixelIndefiniteSpinner : public ProgressVisualizerBase, public IIndefiniteSpinner 505 { 506 SixelIndefiniteSpinner(BaseStream& stream, bool enableVT) : 507 ProgressVisualizerBase(stream, enableVT) 508 { 509 Sixel::RenderControls& renderControls = m_compositor.Controls(); 510 renderControls.RenderSizeInCells(2, 1); 511 512 // Create palette from full image 513 std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets); 514 THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty()); 515 516 // This image matches the target pixel size. If changing the target size, choose the most appropriate image. 517 Sixel::ImageSource wingetIcon{ imageAssetsRoot / "AppList.targetsize-20.png" }; 518 wingetIcon.Resize(renderControls); 519 Sixel::Palette palette = wingetIcon.CreatePalette(renderControls); 520 521 m_folder = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/folders_only.png" }; 522 m_arrow = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/arrow_only.png" }; 523 524 m_folder.Resize(renderControls); 525 m_folder.ApplyPalette(palette); 526 527 Sixel::RenderControls arrowControls = renderControls; 528 arrowControls.InterpolationMode = Sixel::InterpolationMode::Linear; 529 m_arrow.Resize(arrowControls); 530 m_arrow.ApplyPalette(palette); 531 532 m_compositor.Palette(std::move(palette)); 533 m_compositor.AddView(m_arrow.Copy()); 534 m_compositor.AddView(m_folder.Copy()); 535 } 536 537 void ShowSpinner() override 538 { 539 if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) 540 { 541 m_spinnerRunning = true; 542 m_spinnerJob = std::async(std::launch::async, &SixelIndefiniteSpinner::ShowSpinnerInternal, this); 543 } 544 } 545 546 void StopSpinner() override 547 { 548 if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) 549 { 550 m_canceled = true; 551 m_spinnerJob.get(); 552 } 553 } 554 555 void SetMessage(std::string_view message) override 556 { 557 ProgressVisualizerBase::SetMessage(message); 558 } 559 560 std::shared_ptr<Utility::NormalizedString> Message() override 561 { 562 return ProgressVisualizerBase::Message(); 563 } 564 565 private: 566 std::atomic<bool> m_canceled = false; 567 std::atomic<bool> m_spinnerRunning = false; 568 std::future<void> m_spinnerJob; 569 Sixel::ImageSource m_folder; 570 Sixel::ImageSource m_arrow; 571 Sixel::Compositor m_compositor; 572 573 void ShowSpinnerInternal() 574 { 575 // First wait for a small amount of time to enable a fast task to skip 576 // showing anything, or a progress task to skip straight to progress. 577 Sleep(100); 578 579 if (!m_canceled) 580 { 581 // Additional VT-based progress reporting, for terminals that support it 582 m_out << Progress::Construct(Progress::State::Indeterminate); 583 584 // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. 585 std::string_view indent = " "; 586 std::shared_ptr<Utility::NormalizedString> message = ProgressVisualizerBase::Message(); 587 size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0; 588 589 UINT imageHeight = m_compositor.Controls().PixelHeight; 590 591 for (size_t i = 0; !m_canceled; ++i) 592 { 593 m_out << '\r' << indent; 594 595 // Move arrow down one pixel each time 596 m_compositor[0].Translate(0, i % imageHeight, true); 597 m_compositor.RenderTo(m_out); 598 599 message = ProgressVisualizerBase::Message(); 600 size_t newLength = (message ? Utility::UTF8ColumnWidth(*message) : 0); 601 602 std::string eraser; 603 if (newLength < messageLength) 604 { 605 eraser = std::string(messageLength - newLength, ' '); 606 } 607 608 messageLength = newLength; 609 610 m_out << VirtualTerminal::Cursor::Position::Forward(3) << (message ? *message : std::string{}) << eraser << std::flush; 611 Sleep(100); 612 } 613 614 ClearLine(); 615 616 m_out << Progress::Construct(Progress::State::None); 617 } 618 619 m_canceled = false; 620 m_spinnerRunning = false; 621 } 622 }; 623 624 // Displays progress with a sixel image. 625 class SixelProgressBar : public ProgressVisualizerBase, public IProgressBar 626 { 627 public: 628 SixelProgressBar(BaseStream& stream, bool enableVT) : 629 ProgressVisualizerBase(stream, enableVT) 630 { 631 static constexpr UINT s_colorsForBelt = 20; 632 633 Sixel::RenderControls imageRenderControls; 634 imageRenderControls.RenderSizeInCells(2, 1); 635 636 // This image matches the target pixel size. If changing the target size, choose the most appropriate image. 637 std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets); 638 THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty()); 639 640 m_icon = Sixel::ImageSource{ imageAssetsRoot / "AppList.targetsize-20.png" }; 641 m_icon.Resize(imageRenderControls); 642 imageRenderControls.ColorCount = Sixel::Palette::MaximumColorCount - s_colorsForBelt; 643 Sixel::Palette iconPalette = m_icon.CreatePalette(imageRenderControls); 644 645 // TODO: Move to real location 646 m_belt = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/conveyor.png" }; 647 m_belt.Resize(imageRenderControls); 648 imageRenderControls.ColorCount = s_colorsForBelt; 649 imageRenderControls.InterpolationMode = Sixel::InterpolationMode::Linear; 650 Sixel::Palette beltPalette = m_belt.CreatePalette(imageRenderControls); 651 652 Sixel::Palette combinedPalette{ iconPalette, beltPalette }; 653 654 m_icon.ApplyPalette(combinedPalette); 655 m_belt.ApplyPalette(combinedPalette); 656 657 m_compositor.Palette(std::move(combinedPalette)); 658 m_compositor.AddView(m_icon.Copy()); 659 m_compositor.AddView(m_belt.Copy()); 660 m_compositor.Controls().TransparencyEnabled = false; 661 m_compositor.Controls().RenderSizeInCells(s_ProgressBarCellWidth, 1); 662 } 663 664 void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override 665 { 666 if (current < m_lastCurrent) 667 { 668 ClearLine(); 669 } 670 671 m_out << TextFormat::Default; 672 673 m_out << "\r "; 674 675 if (maximum) 676 { 677 678 double percentage = static_cast<double>(current) / maximum; 679 680 // Translate icon so that its leading edge is the progress line 681 INT translation = static_cast<INT>((percentage * m_compositor.Controls().PixelWidth) - m_compositor[0].Width()); 682 683 m_compositor[0].Translate(translation, 0, false); 684 m_compositor[1].Translate(translation, 0, true); 685 m_compositor.RenderTo(m_out); 686 687 m_out << VirtualTerminal::Cursor::Position::Forward(s_ProgressBarCellWidth + 2); 688 689 switch (type) 690 { 691 case AppInstaller::ProgressType::Bytes: 692 OutputBytes(m_out, current); 693 m_out << " / "; 694 OutputBytes(m_out, maximum); 695 break; 696 case AppInstaller::ProgressType::Percent: 697 default: 698 m_out << static_cast<int>(percentage * 100) << '%'; 699 break; 700 } 701 702 // Additional VT-based progress reporting, for terminals that support it 703 m_out << Progress::Construct(Progress::State::Normal, static_cast<int>(percentage * 100)); 704 } 705 else 706 { 707 switch (type) 708 { 709 case AppInstaller::ProgressType::Bytes: 710 OutputBytes(m_out, current); 711 break; 712 case AppInstaller::ProgressType::Percent: 713 m_out << current << '%'; 714 break; 715 default: 716 m_out << current << " unknowns"; 717 break; 718 } 719 } 720 721 m_lastCurrent = current; 722 m_isVisible = true; 723 } 724 725 void EndProgress(bool hideProgressWhenDone) override 726 { 727 if (m_isVisible) 728 { 729 if (hideProgressWhenDone) 730 { 731 ClearLine(); 732 } 733 else 734 { 735 m_out << std::endl; 736 } 737 738 if (VT_Enabled()) 739 { 740 // We always clear the VT-based progress bar, even if hideProgressWhenDone is false 741 // since it would be confusing for users if progress continues to be shown after winget exits 742 // (it is typically not automatically cleared by terminals on process exit) 743 m_out << Progress::Construct(Progress::State::None); 744 } 745 746 m_isVisible = false; 747 } 748 } 749 750 private: 751 std::atomic<bool> m_isVisible = false; 752 uint64_t m_lastCurrent = 0; 753 Sixel::ImageSource m_icon; 754 Sixel::ImageSource m_belt; 755 Sixel::Compositor m_compositor; 756 }; 757 758 std::unique_ptr<IIndefiniteSpinner> IIndefiniteSpinner::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function<bool()>& sixelSupported) 759 { 760 std::unique_ptr<IIndefiniteSpinner> result; 761 762 switch (style) 763 { 764 case VisualStyle::NoVT: 765 case VisualStyle::Retro: 766 case VisualStyle::Accent: 767 case VisualStyle::Rainbow: 768 result = std::make_unique<CharacterIndefiniteSpinner>(stream, enableVT, style); 769 break; 770 case VisualStyle::Sixel: 771 if (sixelSupported()) 772 { 773 try 774 { 775 result = std::make_unique<SixelIndefiniteSpinner>(stream, enableVT); 776 } 777 CATCH_LOG(); 778 } 779 780 if (!result) 781 { 782 result = std::make_unique<CharacterIndefiniteSpinner>(stream, enableVT, VisualStyle::Accent); 783 } 784 break; 785 case VisualStyle::Disabled: 786 break; 787 default: 788 THROW_HR(E_NOTIMPL); 789 } 790 791 return result; 792 } 793 794 std::unique_ptr<IProgressBar> IProgressBar::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function<bool()>& sixelSupported) 795 { 796 std::unique_ptr<IProgressBar> result; 797 798 switch (style) 799 { 800 case VisualStyle::NoVT: 801 case VisualStyle::Retro: 802 case VisualStyle::Accent: 803 case VisualStyle::Rainbow: 804 result = std::make_unique<CharacterProgressBar>(stream, enableVT, style); 805 break; 806 case VisualStyle::Sixel: 807 if (sixelSupported()) 808 { 809 try 810 { 811 result = std::make_unique<SixelProgressBar>(stream, enableVT); 812 } 813 CATCH_LOG(); 814 } 815 816 if (!result) 817 { 818 result = std::make_unique<CharacterProgressBar>(stream, enableVT, VisualStyle::Accent); 819 } 820 break; 821 case VisualStyle::Disabled: 822 break; 823 default: 824 THROW_HR(E_NOTIMPL); 825 } 826 827 return result; 828 } 829 }