| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "minios/screen_base.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| |
| namespace screens { |
| |
| // Colors |
| const char kMenuBlack[] = "0x202124"; |
| const char kMenuBlue[] = "0x8AB4F8"; |
| const char kMenuGrey[] = "0x3F4042"; |
| const char kMenuButtonFrameGrey[] = "0x9AA0A6"; |
| |
| // Dimension Constants |
| const int kMonospaceGlyphWidth = 10; |
| const int kDefaultMessageWidth = 720; |
| |
| // Frecon constants |
| constexpr int kFreconScalingFactor = 1; |
| constexpr int kCanvasSize = 1080; |
| |
| namespace { |
| constexpr char kConsole0[] = "dev/pts/0"; |
| |
| // Dimensions and spacing. |
| constexpr int kDefaultButtonWidth = 80; |
| constexpr int kButtonHeight = 32; |
| constexpr int kMonospaceGlyphHeight = 20; |
| constexpr int kNewLineChar = 10; |
| |
| constexpr char kButtonWidthToken[] = "DEBUG_OPTIONS_BTN_WIDTH"; |
| } // namespace |
| |
| bool ScreenBase::ShowText(const std::string& text, |
| int glyph_offset_h, |
| int glyph_offset_v, |
| const std::string& color) { |
| base::FilePath glyph_dir = screens_path_.Append("glyphs").Append(color); |
| const int kTextStart = glyph_offset_h; |
| |
| for (const auto& chr : text) { |
| int char_num = static_cast<int>(chr); |
| base::FilePath chr_file_path = |
| glyph_dir.Append(base::NumberToString(char_num) + ".png"); |
| if (char_num == kNewLineChar) { |
| glyph_offset_v += kMonospaceGlyphHeight; |
| glyph_offset_h = kTextStart; |
| } else { |
| int offset_rtl = right_to_left_ ? -glyph_offset_h : glyph_offset_h; |
| if (!ShowImage(chr_file_path, offset_rtl, glyph_offset_v)) { |
| LOG(ERROR) << "Failed to show image " << chr_file_path << " for text " |
| << text; |
| return false; |
| } |
| glyph_offset_h += kMonospaceGlyphWidth; |
| } |
| } |
| return true; |
| } |
| |
| bool ScreenBase::ShowImage(const base::FilePath& image_name, |
| int offset_x, |
| int offset_y) { |
| if (right_to_left_) |
| offset_x = -offset_x; |
| std::string command = base::StringPrintf( |
| "\033]image:file=%s;offset=%d,%d;scale=%d\a", image_name.value().c_str(), |
| offset_x, offset_y, frecon_scale_factor_); |
| if (!base::AppendToFile(base::FilePath(root_).Append(kConsole0), |
| command.c_str(), command.size())) { |
| LOG(ERROR) << "Could not write " << image_name << " to console."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ScreenBase::ShowBox(int offset_x, |
| int offset_y, |
| int size_x, |
| int size_y, |
| const std::string& color) { |
| size_x = std::max(size_x, 1); |
| size_y = std::max(size_y, 1); |
| if (right_to_left_) |
| offset_x = -offset_x; |
| |
| std::string command = base::StringPrintf( |
| "\033]box:color=%s;size=%d,%d;offset=%d,%d;scale=%d\a", color.c_str(), |
| size_x, size_y, offset_x, offset_y, frecon_scale_factor_); |
| |
| if (!base::AppendToFile(base::FilePath(root_).Append(kConsole0), |
| command.c_str(), command.size())) { |
| LOG(ERROR) << "Could not write show box command to console."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ScreenBase::ShowMessage(const std::string& message_token, |
| int offset_x, |
| int offset_y) { |
| // Determine the filename of the message resource. Fall back to en-US if |
| // the localized version of the message is not available. |
| base::FilePath message_file_path = |
| screens_path_.Append(locale_).Append(message_token + ".png"); |
| if (!base::PathExists(message_file_path)) { |
| if (locale_ == "en-US") { |
| LOG(ERROR) << "Message " << message_token |
| << " not found in en-US. No fallback available."; |
| return false; |
| } |
| LOG(WARNING) << "Could not find " << message_token << " in " << locale_ |
| << " trying default locale en-US."; |
| message_file_path = |
| screens_path_.Append("en-US").Append(message_token + ".png"); |
| if (!base::PathExists(message_file_path)) { |
| LOG(ERROR) << "Message " << message_token << " not found in path " |
| << message_file_path; |
| return false; |
| } |
| } |
| return ShowImage(message_file_path, offset_x, offset_y); |
| } |
| |
| void ScreenBase::ShowInstructions(const std::string& message_token) { |
| const int kXOffset = (-frecon_canvas_size_ / 2) + (kDefaultMessageWidth / 2); |
| const int kYOffset = (-frecon_canvas_size_ / 2) + 283; |
| if (!ShowMessage(message_token, kXOffset, kYOffset)) |
| LOG(WARNING) << "Unable to show " << message_token; |
| } |
| |
| void ScreenBase::ShowInstructionsWithTitle(const std::string& message_token) { |
| const int kXOffset = (-frecon_canvas_size_ / 2) + (kDefaultMessageWidth / 2); |
| |
| int title_height; |
| if (!GetDimension("TITLE_" + message_token + "_HEIGHT", &title_height)) { |
| title_height = 40; |
| LOG(WARNING) << "Unable to get title constant for " << message_token |
| << ". Defaulting to " << title_height; |
| } |
| int desc_height; |
| if (!GetDimension("DESC_" + message_token + "_HEIGHT", &desc_height)) { |
| desc_height = 40; |
| LOG(WARNING) << "Unable to get description constant for " << message_token |
| << ". Defaulting to " << desc_height; |
| } |
| |
| const int kTitleY = (-frecon_canvas_size_ / 2) + 220 + (title_height / 2); |
| const int kDescY = kTitleY + (title_height / 2) + 16 + (desc_height / 2); |
| if (!ShowMessage("title_" + message_token, kXOffset, kTitleY)) |
| LOG(WARNING) << "Unable to show title " << message_token; |
| if (!ShowMessage("desc_" + message_token, kXOffset, kDescY)) |
| LOG(WARNING) << "Unable to show description " << message_token; |
| } |
| |
| void ScreenBase::ShowProgressPercentage(double progress) { |
| if (progress < 0 || progress > 1) { |
| LOG(WARNING) << "Invalid value of progress: " << progress; |
| return; |
| } |
| constexpr int kProgressIncrement = 10; |
| constexpr int kProgressHeight = 4; |
| constexpr int kLeftIncrement = -500; |
| int progress_length = kProgressIncrement * progress * 100; |
| ShowBox(kLeftIncrement + progress_length / 2, 0, progress_length, |
| kProgressHeight, kMenuBlue); |
| } |
| |
| void ScreenBase::ClearMainArea() { |
| constexpr int kFooterHeight = 142; |
| if (!ShowBox(0, -kFooterHeight / 2, frecon_canvas_size_ + 100, |
| (frecon_canvas_size_ - kFooterHeight), kMenuBlack)) |
| LOG(WARNING) << "Could not clear main area."; |
| } |
| |
| void ScreenBase::ClearScreen() { |
| if (!ShowBox(0, 0, frecon_canvas_size_ + 100, frecon_canvas_size_, |
| kMenuBlack)) |
| LOG(WARNING) << "Could not clear screen."; |
| } |
| |
| void ScreenBase::ShowButton(const std::string& message_token, |
| int offset_y, |
| bool is_selected, |
| int inner_width, |
| bool is_text) { |
| const int kBtnPadding = 32; // Left and right padding. |
| int left_padding_x = (-frecon_canvas_size_ / 2) + (kBtnPadding / 2); |
| const int kOffsetX = left_padding_x + (kBtnPadding / 2) + (inner_width / 2); |
| int right_padding_x = kOffsetX + (kBtnPadding / 2) + (inner_width / 2); |
| // Clear previous state. |
| if (!ShowBox(kOffsetX, offset_y, (kBtnPadding * 2 + inner_width), |
| kButtonHeight, kMenuBlack)) { |
| LOG(WARNING) << "Could not clear button area."; |
| } |
| |
| if (right_to_left_) { |
| std::swap(left_padding_x, right_padding_x); |
| } |
| |
| if (is_selected) { |
| ShowImage(screens_path_.Append("btn_bg_left_focused.png"), left_padding_x, |
| offset_y); |
| ShowImage(screens_path_.Append("btn_bg_right_focused.png"), right_padding_x, |
| offset_y); |
| |
| ShowBox(kOffsetX, offset_y, inner_width, kButtonHeight, kMenuBlue); |
| if (is_text) { |
| ShowText(message_token, left_padding_x, offset_y, "black"); |
| } else { |
| ShowMessage(message_token + "_focused", kOffsetX, offset_y); |
| } |
| } else { |
| ShowImage(screens_path_.Append("btn_bg_left.png"), left_padding_x, |
| offset_y); |
| ShowImage(screens_path_.Append("btn_bg_right.png"), right_padding_x, |
| offset_y); |
| ShowBox(kOffsetX, offset_y - (kButtonHeight / 2) + 1, inner_width, 1, |
| kMenuButtonFrameGrey); |
| ShowBox(kOffsetX, offset_y + (kButtonHeight / 2), inner_width, 1, |
| kMenuButtonFrameGrey); |
| if (is_text) { |
| ShowText(message_token, left_padding_x, offset_y, "white"); |
| } else { |
| ShowMessage(message_token, kOffsetX, offset_y); |
| } |
| } |
| } |
| |
| void ScreenBase::ShowStepper(const std::vector<std::string>& steps) { |
| // The icon real size is 24x24, but it occupies a 36x36 block. Use 36 here for |
| // simplicity. |
| constexpr int kIconSize = 36; |
| constexpr int kSeparatorLength = 46; |
| constexpr int kPadding = 6; |
| |
| int stepper_x = (-frecon_canvas_size_ / 2) + (kIconSize / 2); |
| constexpr int kStepperXStep = kIconSize + kSeparatorLength + (kPadding * 2); |
| const int kStepperY = 144 - (frecon_canvas_size_ / 2); |
| int separator_x = (-frecon_canvas_size_ / 2) + kIconSize + kPadding + |
| (kSeparatorLength / 2); |
| |
| for (const auto& step : steps) { |
| base::FilePath stepper_image = screens_path_.Append("ic_" + step + ".png"); |
| if (!base::PathExists(stepper_image)) { |
| // TODO(vyshu): Create a new generic icon to be used instead of done. |
| LOG(WARNING) << "Stepper icon " << stepper_image |
| << " not found. Defaulting to the done icon."; |
| stepper_image = screens_path_.Append("ic_done.png"); |
| if (!base::PathExists(stepper_image)) { |
| LOG(ERROR) << "Could not find stepper icon done. Cannot show stepper."; |
| return; |
| } |
| } |
| ShowImage(stepper_image, stepper_x, kStepperY); |
| stepper_x += kStepperXStep; |
| } |
| |
| for (int i = 0; i < steps.size() - 1; ++i) { |
| ShowBox(separator_x, kStepperY, kSeparatorLength, 1, kMenuGrey); |
| separator_x += kStepperXStep; |
| } |
| } |
| |
| void ScreenBase::ReadDimensionConstants() { |
| image_dimensions_.clear(); |
| base::FilePath path = screens_path_.Append(locale_).Append("constants.sh"); |
| std::string dimension_consts; |
| if (!ReadFileToString(path, &dimension_consts)) { |
| LOG(ERROR) << "Could not read constants.sh file for language " << locale_; |
| return; |
| } |
| if (!base::SplitStringIntoKeyValuePairs(dimension_consts, '=', '\n', |
| &image_dimensions_)) { |
| LOG(WARNING) << "Unable to parse all dimension information for " << locale_; |
| return; |
| } |
| |
| // Save default button width for this locale. |
| if (!GetDimension(kButtonWidthToken, &default_button_width_)) { |
| default_button_width_ = kDefaultButtonWidth; |
| LOG(WARNING) << "Unable to get dimension for " << kButtonWidthToken |
| << ". Defaulting to width " << kDefaultButtonWidth; |
| } |
| } |
| |
| bool ScreenBase::GetDimension(const std::string& token, int* token_dimension) { |
| if (image_dimensions_.empty()) { |
| LOG(ERROR) << "No dimensions available."; |
| return false; |
| } |
| |
| // Find the dimension for the token. |
| for (const auto& dimension : image_dimensions_) { |
| if (dimension.first == token) { |
| if (!base::StringToInt(dimension.second, token_dimension)) { |
| LOG(ERROR) << "Could not convert " << dimension.second |
| << " to a number."; |
| return false; |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void ScreenBase::GetFreconConstants() { |
| base::FilePath scale_factor_path = |
| root_.Append("etc").Append("frecon").Append("scale"); |
| std::string frecon_scale_factor; |
| if (!ReadFileToString(scale_factor_path, &frecon_scale_factor)) { |
| frecon_scale_factor_ = kFreconScalingFactor; |
| LOG(WARNING) << "Could not read frecon scale factor from /etc. Defaulting " |
| "to scale " |
| << kFreconScalingFactor; |
| } else { |
| base::TrimString(frecon_scale_factor, " \n", &frecon_scale_factor); |
| if (!base::StringToInt(frecon_scale_factor, &frecon_scale_factor_)) { |
| frecon_scale_factor_ = kFreconScalingFactor; |
| LOG(WARNING) << "Could not convert " << frecon_scale_factor_ |
| << " to an int. Defaulting to scale " |
| << kFreconScalingFactor; |
| } |
| } |
| |
| base::FilePath canvas_size_path = |
| root_.Append("etc").Append("frecon").Append("size"); |
| std::string frecon_canvas_size; |
| if (!ReadFileToString(canvas_size_path, &frecon_canvas_size)) { |
| frecon_canvas_size_ = kCanvasSize; |
| LOG(WARNING) << "Could not read frecon canvas size from /etc/frecon." |
| << " Defaulting to canvas size " << kCanvasSize; |
| } else { |
| base::TrimString(frecon_canvas_size, " \n", &frecon_canvas_size); |
| if (!base::StringToInt(frecon_canvas_size, &frecon_canvas_size_)) { |
| frecon_canvas_size_ = kCanvasSize; |
| LOG(WARNING) << "Could not convert " << frecon_canvas_size |
| << " to int. Defaulting to canvas size " << kCanvasSize; |
| } |
| } |
| } |
| } // namespace screens |