910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/examples/layout_example_base.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/examples/example_combobox_model.h"
#include "ui/views/examples/grit/views_examples_resources.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view_class_properties.h"

namespace views::examples {

namespace {

constexpr int kLayoutExampleVerticalSpacing = 3;
constexpr int kLayoutExampleLeftPadding = 8;
constexpr gfx::Size kLayoutExampleDefaultChildSize(180, 90);

class LayoutPanel : public View {
  METADATA_HEADER(LayoutPanel, View)

 protected:
  void OnThemeChanged() override {
    View::OnThemeChanged();
    SetBorder(CreateSolidBorder(
        1, GetColorProvider()->GetColor(ui::kColorFocusableBorderUnfocused)));
  }
};

BEGIN_METADATA(LayoutPanel)
END_METADATA

std::unique_ptr<Textfield> CreateCommonTextfield(
    TextfieldController* container) {
  auto textfield = std::make_unique<Textfield>();
  textfield->SetDefaultWidthInChars(3);
  textfield->SetTextInputType(ui::TEXT_INPUT_TYPE_NUMBER);
  textfield->SetText(u"0");
  textfield->set_controller(container);
  return textfield;
}

std::unique_ptr<Textfield> CreateCommonTextfieldWithAXName(
    TextfieldController* container,
    std::u16string name) {
  auto text_field = CreateCommonTextfield(container);
  text_field->GetViewAccessibility().SetName(name);
  return text_field;
}

}  // namespace

void LayoutExampleBase::InsetTextfields::ResetControllers() {
  if (left) {
    left->set_controller(nullptr);
  }
  if (top) {
    top->set_controller(nullptr);
  }
  if (right) {
    right->set_controller(nullptr);
  }
  if (bottom) {
    bottom->set_controller(nullptr);
  }
}

LayoutExampleBase::ChildPanel::ChildPanel(LayoutExampleBase* example)
    : example_(example) {
  margin_.left = CreateTextfield(u"Left margin");
  margin_.top = CreateTextfield(u"Top margin");
  margin_.right = CreateTextfield(u"Right margin");
  margin_.bottom = CreateTextfield(u"Bottom margin");
  flex_ = CreateTextfield(u"Flex");
  flex_->SetText(std::u16string());
  SetLayoutManager(std::make_unique<DelegatingLayoutManager>(this));
}

LayoutExampleBase::ChildPanel::~ChildPanel() = default;

bool LayoutExampleBase::ChildPanel::OnMousePressed(
    const ui::MouseEvent& event) {
  if (event.IsOnlyLeftMouseButton()) {
    SetSelected(true);
  }
  return true;
}

ProposedLayout LayoutExampleBase::ChildPanel::CalculateProposedLayout(
    const SizeBounds& size_bounds) const {
  ProposedLayout layout;
  if (!size_bounds.is_fully_bounded()) {
    layout.host_size = gfx::Size();
    return layout;
  } else {
    layout.host_size =
        gfx::Size(size_bounds.width().value(), size_bounds.height().value());
  }

  constexpr int kSpacing = 2;
  if (selected_) {
    gfx::Size preferred_size = margin_.top->GetPreferredSize({});
    layout.child_layouts.emplace_back(
        margin_.top.get(), margin_.top->GetVisible(),
        gfx::Rect((layout.host_size.width() - preferred_size.width()) / 2,
                  kSpacing, preferred_size.width(), preferred_size.height()));
    preferred_size = margin_.left->GetPreferredSize({});
    layout.child_layouts.emplace_back(
        margin_.left.get(), margin_.left->GetVisible(),
        gfx::Rect(kSpacing,
                  (layout.host_size.height() - preferred_size.height()) / 2,
                  preferred_size.width(), preferred_size.height()));
    preferred_size = margin_.bottom->GetPreferredSize({});
    layout.child_layouts.emplace_back(
        margin_.bottom.get(), margin_.bottom->GetVisible(),
        gfx::Rect(
            (layout.host_size.width() - preferred_size.width()) / 2,
            layout.host_size.height() - preferred_size.height() - kSpacing,
            preferred_size.width(), preferred_size.height()));
    preferred_size = margin_.right->GetPreferredSize({});
    layout.child_layouts.emplace_back(
        margin_.right.get(), margin_.right->GetVisible(),
        gfx::Rect(layout.host_size.width() - preferred_size.width() - kSpacing,
                  (layout.host_size.height() - preferred_size.height()) / 2,
                  preferred_size.width(), preferred_size.height()));
    preferred_size = flex_->GetPreferredSize({});
    layout.child_layouts.emplace_back(
        flex_.get(), flex_->GetVisible(),
        gfx::Rect((layout.host_size.width() - preferred_size.width()) / 2,
                  (layout.host_size.height() - preferred_size.height()) / 2,
                  preferred_size.width(), preferred_size.height()));
  }
  return layout;
}

void LayoutExampleBase::ChildPanel::SetSelected(bool value) {
  if (value != selected_) {
    selected_ = value;
    OnThemeChanged();
    if (selected_ && parent()) {
      for (View* child : parent()->children()) {
        if (child != this && child->GetGroup() == GetGroup()) {
          static_cast<ChildPanel*>(child)->SetSelected(false);
        }
      }
    }
    margin_.left->SetVisible(selected_);
    margin_.top->SetVisible(selected_);
    margin_.right->SetVisible(selected_);
    margin_.bottom->SetVisible(selected_);
    flex_->SetVisible(selected_);
    InvalidateLayout();
    example_->RefreshLayoutPanel(false);
  }
}

int LayoutExampleBase::ChildPanel::GetFlex() const {
  int flex;
  return base::StringToInt(flex_->GetText(), &flex) ? flex : -1;
}

void LayoutExampleBase::ChildPanel::OnThemeChanged() {
  View::OnThemeChanged();
  SetBorder(CreateSolidBorder(
      1, GetColorProvider()->GetColor(
             selected_ ? ui::kColorFocusableBorderFocused
                       : ui::kColorFocusableBorderUnfocused)));
}

void LayoutExampleBase::ChildPanel::ContentsChanged(
    Textfield* sender,
    const std::u16string& new_contents) {
  const gfx::Insets margins = LayoutExampleBase::TextfieldsToInsets(margin_);
  if (!margins.IsEmpty()) {
    SetProperty(kMarginsKey, margins);
  } else {
    ClearProperty(kMarginsKey);
  }
  example_->RefreshLayoutPanel(sender == flex_);
}

Textfield* LayoutExampleBase::ChildPanel::CreateTextfield(
    const std::u16string& name) {
  return AddChildView(CreateCommonTextfieldWithAXName(this, name));
}

BEGIN_METADATA(LayoutExampleBase, ChildPanel)
END_METADATA

LayoutExampleBase::LayoutExampleBase(const char* title) : ExampleBase(title) {}

LayoutExampleBase::~LayoutExampleBase() {
  if (layout_panel_) {
    layout_panel_->RemoveAllChildViews();
  }
  if (preferred_width_view_) {
    preferred_width_view_->set_controller(nullptr);
  }
  if (preferred_height_view_) {
    preferred_height_view_->set_controller(nullptr);
  }
}

void LayoutExampleBase::RefreshLayoutPanel(bool update_layout) {
  if (update_layout) {
    UpdateLayoutManager();
  }
  layout_panel_->InvalidateLayout();
  layout_panel_->SchedulePaint();
}

gfx::Insets LayoutExampleBase::TextfieldsToInsets(
    const InsetTextfields& textfields,
    const gfx::Insets& default_insets) {
  int top, left, bottom, right;
  if (!base::StringToInt(textfields.top->GetText(), &top)) {
    top = default_insets.top();
  }
  if (!base::StringToInt(textfields.left->GetText(), &left)) {
    left = default_insets.left();
  }
  if (!base::StringToInt(textfields.bottom->GetText(), &bottom)) {
    bottom = default_insets.bottom();
  }
  if (!base::StringToInt(textfields.right->GetText(), &right)) {
    right = default_insets.right();
  }
  return gfx::Insets::TLBR(std::max(0, top), std::max(0, left),
                           std::max(0, bottom), std::max(0, right));
}

Combobox* LayoutExampleBase::CreateAndAddCombobox(
    const std::u16string& label_text,
    base::span<const char* const> items,
    base::RepeatingClosure combobox_callback) {
  auto* const row = control_panel_->AddChildView(std::make_unique<View>());
  row->SetLayoutManager(std::make_unique<BoxLayout>(
      BoxLayout::Orientation::kHorizontal, gfx::Insets(),
      kLayoutExampleVerticalSpacing));
  row->AddChildView(std::make_unique<Label>(label_text));
  auto* const combobox = row->AddChildView(std::make_unique<Combobox>(
      std::make_unique<ExampleComboboxModel>(items)));
  combobox->SetCallback(std::move(combobox_callback));
  combobox->GetViewAccessibility().SetName(label_text);
  return combobox;
}

Textfield* LayoutExampleBase::CreateAndAddTextfield(
    const std::u16string& label_text) {
  auto* const row = control_panel_->AddChildView(std::make_unique<View>());
  row->SetLayoutManager(std::make_unique<BoxLayout>(
      BoxLayout::Orientation::kHorizontal, gfx::Insets(),
      kLayoutExampleVerticalSpacing));
  auto* label = row->AddChildView(std::make_unique<Label>(label_text));
  auto* text_field = row->AddChildView(CreateCommonTextfield(this));
  text_field->GetViewAccessibility().SetName(*label);
  return text_field;
}

void LayoutExampleBase::CreateMarginsTextFields(const std::u16string& label,
                                                InsetTextfields* textfields) {
  auto* const row = control_panel_->AddChildView(std::make_unique<View>());
  row->SetLayoutManager(std::make_unique<BoxLayout>(
      BoxLayout::Orientation::kHorizontal, gfx::Insets(),
      kLayoutExampleVerticalSpacing));
  row->AddChildView(std::make_unique<Label>(label));

  auto* const container = row->AddChildView(std::make_unique<View>());
  container
      ->SetLayoutManager(std::make_unique<BoxLayout>(
          BoxLayout::Orientation::kVertical, gfx::Insets(),
          kLayoutExampleVerticalSpacing))
      ->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kCenter);
  textfields->top = container->AddChildView(CreateCommonTextfieldWithAXName(
      this,
      label + u" " + l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_TOP_LABEL)));
  auto* const middle_row = container->AddChildView(std::make_unique<View>());
  middle_row->SetLayoutManager(std::make_unique<BoxLayout>(
      BoxLayout::Orientation::kHorizontal, gfx::Insets(),
      kLayoutExampleVerticalSpacing));
  textfields->left = middle_row->AddChildView(CreateCommonTextfieldWithAXName(
      this,
      label + u" " + l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_LEFT_LABEL)));
  textfields->right = middle_row->AddChildView(CreateCommonTextfieldWithAXName(
      this,
      label + u" " + l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_RIGHT_LABEL)));
  textfields->bottom = container->AddChildView(CreateCommonTextfieldWithAXName(
      this,
      label + u" " + l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_BOTTOM_LABEL)));
}

Checkbox* LayoutExampleBase::CreateAndAddCheckbox(
    const std::u16string& label_text,
    base::RepeatingClosure checkbox_callback) {
  return control_panel_->AddChildView(
      std::make_unique<Checkbox>(label_text, std::move(checkbox_callback)));
}

void LayoutExampleBase::CreateExampleView(View* container) {
  container->SetLayoutManager(std::make_unique<FillLayout>());
  View* full_panel = container->AddChildView(std::make_unique<View>());

  auto* const manager = full_panel->SetLayoutManager(
      std::make_unique<BoxLayout>(views::BoxLayout::Orientation::kHorizontal));
  layout_panel_ = full_panel->AddChildView(std::make_unique<LayoutPanel>());
  // Expand the layout panel as much as possible.
  manager->SetFlexForView(layout_panel_, 1);

  control_panel_ = full_panel->AddChildView(std::make_unique<View>());
  // Used the preferred width for control panel.
  manager->SetFlexForView(control_panel_, 0);
  control_panel_
      ->SetLayoutManager(std::make_unique<BoxLayout>(
          BoxLayout::Orientation::kVertical,
          gfx::Insets::VH(kLayoutExampleVerticalSpacing,
                          kLayoutExampleLeftPadding),
          kLayoutExampleVerticalSpacing))
      ->set_cross_axis_alignment(LayoutAlignment::kStart);

  auto* const row = control_panel_->AddChildView(std::make_unique<View>());
  row->SetLayoutManager(std::make_unique<BoxLayout>(
                            BoxLayout::Orientation::kHorizontal, gfx::Insets(),
                            kLayoutExampleVerticalSpacing))
      ->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kCenter);
  add_button_ = row->AddChildView(std::make_unique<MdTextButton>(
      base::BindRepeating(&LayoutExampleBase::AddButtonPressed,
                          base::Unretained(this)),
      l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_ADD_LABEL)));

  preferred_width_view_ = row->AddChildView(CreateCommonTextfieldWithAXName(
      this, l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_PREFERRED_WIDTH_LABEL)));
  preferred_width_view_->SetText(
      base::NumberToString16(kLayoutExampleDefaultChildSize.width()));

  preferred_height_view_ = row->AddChildView(CreateCommonTextfieldWithAXName(
      this, l10n_util::GetStringUTF16(IDS_LAYOUT_BASE_PREFERRED_HEIGHT_LABEL)));
  preferred_height_view_->SetText(
      base::NumberToString16(kLayoutExampleDefaultChildSize.height()));

  CreateAdditionalControls();
}

gfx::Size LayoutExampleBase::GetNewChildPanelPreferredSize() {
  int width;
  if (!base::StringToInt(preferred_width_view_->GetText(), &width)) {
    width = kLayoutExampleDefaultChildSize.width();
  }

  int height;
  if (!base::StringToInt(preferred_height_view_->GetText(), &height)) {
    height = kLayoutExampleDefaultChildSize.height();
  }

  return gfx::Size(std::max(0, width), std::max(0, height));
}

void LayoutExampleBase::AddButtonPressed() {
  auto* const panel =
      layout_panel_->AddChildView(std::make_unique<ChildPanel>(this));
  panel->SetPreferredSize(GetNewChildPanelPreferredSize());
  constexpr int kChildPanelGroup = 100;
  panel->SetGroup(kChildPanelGroup);
  RefreshLayoutPanel(false);
}

}  // namespace views::examples