blob: 9c7a041111279a7adf542cd4e42109004615087a [file] [log] [blame]
// 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